diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 00000000000..5cdd32a553a --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,7 @@ +;;; Directory Local Variables -*- no-byte-compile: t -*- +;;; For more information see (info "(emacs) Directory Variables") + +((c++-mode . ((c-file-style . "filament") + (apheleia-inhibit . t))) + (c-mode . ((c-file-style . "filament") + (apheleia-inhibit . t)))) diff --git a/.github/workflows/android-continuous.yml b/.github/workflows/android-continuous.yml index e9aebe8e76c..2ee0ff2b690 100644 --- a/.github/workflows/android-continuous.yml +++ b/.github/workflows/android-continuous.yml @@ -10,7 +10,7 @@ on: jobs: build-android: name: build-android - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 diff --git a/.github/workflows/cocopods-deploy.yml b/.github/workflows/cocopods-deploy.yml index 0bf8e748b8f..26e08c048df 100644 --- a/.github/workflows/cocopods-deploy.yml +++ b/.github/workflows/cocopods-deploy.yml @@ -13,7 +13,7 @@ on: jobs: cocoapods-deploy: name: cocoapods-deploy - runs-on: macos-latest + runs-on: macos-14 steps: - name: Check out iOS/CocoaPods directory uses: Bhacaz/checkout-files@49fc3050859046bf4f4873678d46099985640e89 diff --git a/.github/workflows/ios-continuous.yml b/.github/workflows/ios-continuous.yml index c9bc5bd85bc..680a9a75fd7 100644 --- a/.github/workflows/ios-continuous.yml +++ b/.github/workflows/ios-continuous.yml @@ -10,7 +10,7 @@ on: jobs: build-ios: name: build-ios - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 diff --git a/.github/workflows/mac-continuous.yml b/.github/workflows/mac-continuous.yml index 80402fa5a26..682102e21b4 100644 --- a/.github/workflows/mac-continuous.yml +++ b/.github/workflows/mac-continuous.yml @@ -10,7 +10,7 @@ on: jobs: build-mac: name: build-mac - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 diff --git a/.github/workflows/npm-deploy.yml b/.github/workflows/npm-deploy.yml index 50d27341b89..0fdf1817517 100644 --- a/.github/workflows/npm-deploy.yml +++ b/.github/workflows/npm-deploy.yml @@ -11,7 +11,7 @@ on: jobs: npm-deploy: name: npm-deploy - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 with: diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index 849383a416b..eebd55c2fa8 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - os: [macos-latest, ubuntu-22.04] + os: [macos-14, ubuntu-22.04] steps: - uses: actions/checkout@v3.3.0 @@ -40,7 +40,7 @@ jobs: build-android: name: build-android - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 @@ -54,7 +54,7 @@ jobs: build-ios: name: build-iOS - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 @@ -67,7 +67,7 @@ jobs: build-web: name: build-web - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa12f3d3225..2312795605b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: - os: [macos-latest, ubuntu-22.04] + os: [macos-14, ubuntu-22.04] steps: - name: Decide Git ref @@ -65,7 +65,7 @@ jobs: build-web: name: build-web - runs-on: macos-latest + runs-on: macos-14 if: github.event_name == 'release' || github.event.inputs.platform == 'web' steps: @@ -98,7 +98,7 @@ jobs: build-android: name: build-android - runs-on: macos-latest + runs-on: macos-14 if: github.event_name == 'release' || github.event.inputs.platform == 'android' steps: @@ -129,7 +129,7 @@ jobs: - name: Sign sample-gltf-viewer run: | echo "${APK_KEYSTORE_BASE64}" > filament.jks.base64 - base64 --decode filament.jks.base64 > filament.jks + base64 --decode -i filament.jks.base64 > filament.jks BUILD_TOOLS_VERSION=$(ls ${ANDROID_HOME}/build-tools | sort -V | tail -n 1) APKSIGNER=${ANDROID_HOME}/build-tools/${BUILD_TOOLS_VERSION}/apksigner IN_FILE="out/sample-gltf-viewer-release.apk" @@ -152,7 +152,7 @@ jobs: build-ios: name: build-ios - runs-on: macos-latest + runs-on: macos-14 if: github.event_name == 'release' || github.event.inputs.platform == 'ios' steps: @@ -205,7 +205,7 @@ jobs: TAG: ${{ steps.git_ref.outputs.tag }} run: | build\windows\build-github.bat release - move out\filament-windows.tgz out\filament-$Env:TAG-windows.tgz + move out\filament-windows.tgz out\filament-%TAG%-windows.tgz shell: cmd - uses: actions/github-script@v6 env: diff --git a/.github/workflows/web-continuous.yml b/.github/workflows/web-continuous.yml index ceabe618b24..ef2f539977b 100644 --- a/.github/workflows/web-continuous.yml +++ b/.github/workflows/web-continuous.yml @@ -10,7 +10,7 @@ on: jobs: build-web: name: build-web - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v3.3.0 diff --git a/.gitignore b/.gitignore index 196b33a061f..8a48127ca4c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ settings.json test*.png test*.json results +/compile_commands.json +/.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 24adbb8268d..ed32949873a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,12 @@ option(FILAMENT_LINUX_IS_MOBILE "Treat Linux as Mobile" OFF) option(FILAMENT_ENABLE_ASAN_UBSAN "Enable Address and Undefined Behavior Sanitizers" OFF) +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." ) @@ -65,6 +71,9 @@ set(FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB "8" CACHE STRING "Size of the Metal handle arena, default 8." ) +# Enable exceptions by default in spirv-cross. +set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF) + # ================================================================================================== # CMake policies # ================================================================================================== @@ -225,6 +234,21 @@ if (WIN32) # we don't need them on CI. string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" ${LinkerFlag} ${${LinkerFlag}}) endforeach() + + # We turn off compile-time optimizations for CI, as options that speed up the compile-time + # (e.g. /MP) might increase memory usage, leading to instabilities on limited CI machines. + option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" OFF) + else() + option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" ON) + endif() + + if (MSVC) + if (FILAMENT_SHORTEN_MSVC_COMPILATION) + # enable multi-processor compilation + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + # disable run-time STL checks to improve tools (e.g. matc) performance + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_ITERATOR_DEBUG_LEVEL=0") + endif() endif() endif() @@ -337,6 +361,7 @@ endif() if (CYGWIN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") + set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON) endif() if (MSVC) @@ -373,6 +398,7 @@ endif() # saved by -fno-exception and 10 KiB saved by -fno-rtti). if (ANDROID OR IOS OR WEBGL) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-exceptions -fno-rtti") + set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON) if (ANDROID OR WEBGL) # Omitting unwind info prevents the generation of readable stack traces in crash reports on iOS @@ -384,6 +410,7 @@ endif() # std::visit, which is not supported on iOS 11.0 when exceptions are enabled. if (IOS) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-exceptions") + set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON) endif() # With WebGL, we disable RTTI even for debug builds because we pass emscripten::val back and forth @@ -403,7 +430,13 @@ endif() if (FILAMENT_ENABLE_ASAN_UBSAN) set(EXTRA_SANITIZE_OPTIONS "-fsanitize=address -fsanitize=undefined") endif() - +if (FILAMENT_ENABLE_TSAN) + set(EXTRA_SANITIZE_OPTIONS "-fsanitize=thread") +endif() +if (ANDROID) + # keep STL debug infos (mimics what the NDK does) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-limit-debug-info") +endif() if (NOT MSVC AND NOT WEBGL) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstack-protector") endif() @@ -500,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 # ================================================================================================== @@ -523,6 +571,11 @@ if (FILAMENT_SUPPORTS_METAL) set(MATC_API_FLAGS ${MATC_API_FLAGS} -a metal) endif() +# Disable ESSL 1.0 code generation. +if (NOT FILAMENT_ENABLE_FEATURE_LEVEL_0) + set(MATC_API_FLAGS ${MATC_API_FLAGS} -1) +endif() + # Enable debug info (preserves names in SPIR-V) if (FILAMENT_ENABLE_MATDBG) set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -d) @@ -598,9 +651,9 @@ function(combine_static_libs TARGET OUTPUT DEPS) # Loop through the dependent libraries and query their location on disk. set(DEPS_FILES ) foreach(DEPENDENCY ${DEPS}) - if(TARGET ${DEPENDENCY}) + if (TARGET ${DEPENDENCY}) get_property(dep_type TARGET ${DEPENDENCY} PROPERTY TYPE) - if(dep_type STREQUAL "STATIC_LIBRARY") + if (dep_type STREQUAL "STATIC_LIBRARY") list(APPEND DEPS_FILES "$") endif() endif() @@ -670,7 +723,7 @@ function(get_resgen_vars ARCHIVE_DIR ARCHIVE_NAME) set(RESGEN_OUTPUTS "${OUTPUTS}" PARENT_SCOPE) set(RESGEN_FLAGS -qx ${ARCHIVE_DIR} -p ${ARCHIVE_NAME} PARENT_SCOPE) set(RESGEN_SOURCE "${ARCHIVE_DIR}/${ARCHIVE_NAME}${ASM_SUFFIX}.S" PARENT_SCOPE) - set(RESGEN_SOURCE_FLAGS "-I${ARCHIVE_DIR} ${ASM_ARCH_FLAG}" PARENT_SCOPE) + set(RESGEN_SOURCE_FLAGS "-I'${ARCHIVE_DIR}' ${ASM_ARCH_FLAG}" PARENT_SCOPE) endif() endfunction() @@ -685,7 +738,6 @@ add_subdirectory(${LIBRARIES}/filabridge) add_subdirectory(${LIBRARIES}/filaflat) add_subdirectory(${LIBRARIES}/filagui) add_subdirectory(${LIBRARIES}/filameshio) -add_subdirectory(${LIBRARIES}/geometry) add_subdirectory(${LIBRARIES}/gltfio) add_subdirectory(${LIBRARIES}/ibl) add_subdirectory(${LIBRARIES}/iblprefilter) @@ -713,6 +765,9 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt) add_subdirectory(${EXTERNAL}/stb/tnt) add_subdirectory(${EXTERNAL}/getopt) +# Note that this has to be placed after mikktspace in order for combine_static_libs to work. +add_subdirectory(${LIBRARIES}/geometry) + if (FILAMENT_BUILD_FILAMAT OR IS_HOST_PLATFORM) # spirv-tools must come before filamat, as filamat relies on the presence of the # spirv-tools_SOURCE_DIR variable. @@ -730,6 +785,8 @@ endif() if (FILAMENT_SUPPORTS_VULKAN) add_subdirectory(${LIBRARIES}/bluevk) add_subdirectory(${EXTERNAL}/vkmemalloc/tnt) + set(SPIRV_HEADERS_SKIP_EXAMPLES ON) + add_subdirectory(${EXTERNAL}/spirv-headers) endif() set(FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index adbffd6580c..4a1a9c7fa7e 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,12 +7,3 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut - -- engine: Added parameter for configuring JobSystem thread count -- engine: In Java, introduce Engine.Builder -- gltfio: fix ubershader index for transmission&volume material -- engine: New tone mapper: `AgXTonemapper`. -- matinfo: Add support for viewing ESSL 1.0 shaders -- engine: Add support for stencil buffer when post-processing is disabled (Metal backend only). -- engine: Add `Renderer::getClearOptions()` [b/243846268] -- engine: Fix stable shadows (again) when an IBL rotation is used diff --git a/README.md b/README.md index e35994aa510..9fc2f823a5a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.44.0' + implementation 'com.google.android.filament:filament-android:1.51.3' } ``` @@ -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.44.0' +pod 'Filament', '~> 1.51.3' ``` ### Snapshots diff --git a/RELEASE_GUIDE.md b/RELEASE_GUIDE.md index 0100f0b6f42..478771e6f7a 100644 --- a/RELEASE_GUIDE.md +++ b/RELEASE_GUIDE.md @@ -128,3 +128,15 @@ Navigate to [Filament's release workflow](https://github.com/google/filament/actions/workflows/release.yml). Hit the _Run workflow_ dropdown. Modify _Platform to build_ and _Release tag to build_, then hit _Run workflow_. This will initiate a new release run. + +## 11. Kick off the npm and CocoaPods release jobs + +Navigate to [Filament's npm deploy +workflow](https://github.com/google/filament/actions/workflows/npm-deploy.yml). +Hit the _Run workflow_ dropdown. Modify _Release tag to deploy_ to the tag corresponding to this +release (for example, v1.42.2). + +Navigate to [Filament's CocoaPods deploy +workflow](https://github.com/google/filament/actions/workflows/cocopods-deploy.yml). +Hit the _Run workflow_ dropdown. Modify _Release tag to deploy_ to the tag corresponding to this +release (for example, v1.42.2). diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7d657e90cd9..7dae83f407e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,102 @@ 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.4 + + +## v1.51.3 + + +## v1.51.2 + +- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()` + +## v1.51.1 + + +## v1.51.0 + +- materials: add support for post-lighting mix factor (b/328498606) [⚠️ **New Material Version**] + +## v1.50.6 + +- Add new API `SwapChain::getFrameScheduledCallback` +- vulkan: fixed validation error VUID-vkAcquireNextImageKHR-semaphore-01779 +- opengl: Add support for protected content swapchains and contexts + +## v1.50.5 + +- android: NDK 26.1.10909125 is used by default +- android: Minimum API level on Android is now API 21 instead of API 19. This allows the use of OpenGL ES 3.1 +- rendering: New PBR Neutral tone mapper, designed to preserve materials color appearance +- android: Change default frameRateOptions.interval to 1.0 + +## v1.50.4 + + +## v1.50.3 + + +## v1.50.2 + + +## v1.50.1 + +- Metal: fix some shader artifacts by disabling fast math optimizations. +- backend: remove `atan2` overload which had a typo and wasn't useful. Fixes b/320856413. +- utils: remove usages of `SpinLock`. Fixes b/321101014. + +## v1.50.0 +- engine: TAA now supports 4x upscaling [BETA] [⚠️ **New Material Version**] + +## v1.49.3 + +- matc: Generate stereo variants for FL0 materials [⚠️ **Recompile materials**] + +## v1.49.2 + + +## v1.49.1 + + +## v1.49.0 + +- matc: Fix ESSL 1.0 codegen when using external samplers [⚠️ **Recompile materials**] + +## v1.48.0 + +- matc: New option `-1` to disable generation of ESSL 1.0 code in Feature Level 0 materials +- matc: Support optimizations for ESSL 1.0 code [⚠️ **Recompile materials**] + +## v1.47.0 + +- engine: Support up to 4 side-by-side stereoscopic eyes, configurable at Engine creation time. See + `Engine::Config::stereoscopicEyeCount`. [⚠️ **Recompile Materials**] + +## v1.46.0 + +- engine: Allow instantiating Engine at a given feature level via `Engine::Builder::featureLevel` +- matc: Enable `GL_OES_standard_derivatives` extension in ESSL 1.0 shaders +- matc: Fix code generation of double sided and masked materials in ESSL 1.0 shaders +- filagui: Add support for feature level 0 +- matc: Add support for post-process materials in feature level 0 +- engine: Add `Material::getFeatureLevel()` +- engine: Add missing `Material::getReflectionMode()` method in Java +- engine: Support basic usage of post-processing materials on feature level 0 +- engine: Fix critical GLES 2.0 bugs +- engine: Add `FILAMENT_ENABLE_FEATURE_LEVEL_0` build-time option optionally allow building Filament + without FL0 support. + +## v1.45.1 + +- engine: Added parameter for configuring JobSystem thread count +- engine: In Java, introduce Engine.Builder +- gltfio: fix ubershader index for transmission&volume material +- engine: New tone mapper: `AgXTonemapper`. +- matinfo: Add support for viewing ESSL 1.0 shaders +- engine: Add `Renderer::getClearOptions()` [b/243846268] +- engine: Fix stable shadows (again) when an IBL rotation is used + ## v1.45.0 - materials: fix alpha masked materials when MSAA is turned on [⚠️ **Recompile materials**] diff --git a/android/build.gradle b/android/build.gradle index c233a6766c5..4d6ebb91f83 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -80,15 +80,15 @@ buildscript { ext.versions = [ 'jdk': 17, - 'minSdk': 19, - 'targetSdk': 33, - 'compileSdk': 33, - 'kotlin': '1.9.0', - 'kotlin_coroutines': '1.7.2', + 'minSdk': 21, + 'targetSdk': 34, + 'compileSdk': 34, + 'kotlin': '1.9.21', + 'kotlin_coroutines': '1.7.3', 'buildTools': '34.0.0', - 'ndk': '25.1.8937393', - 'androidx_core': '1.10.1', - 'androidx_annotations': '1.6.0' + 'ndk': '26.1.10909125', + 'androidx_core': '1.12.0', + 'androidx_annotations': '1.7.0' ] ext.deps = [ @@ -104,7 +104,7 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:8.1.0' + classpath 'com.android.tools.build:gradle:8.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" } diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index e5bb4e95fa7..2677e2f8fa2 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -384,6 +384,20 @@ Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass, engine->flushAndWait(); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass, + jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + 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 @@ -428,6 +442,13 @@ Java_com_google_android_filament_Engine_nIsAutomaticInstancingEnabled(JNIEnv*, j return (jboolean)engine->isAutomaticInstancingEnabled(); } +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_Engine_nGetMaxStereoscopicEyes(JNIEnv*, jclass, jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + return (jlong) engine->getMaxStereoscopicEyes(); +} + + extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_Engine_nGetSupportedFeatureLevel(JNIEnv *, jclass, jlong nativeEngine) { @@ -470,7 +491,11 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*, jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB, jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB, - jlong jobSystemThreadCount) { + jlong jobSystemThreadCount, + jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile, + jint stereoscopicType, jlong stereoscopicEyeCount, + jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge, + jboolean disableHandleUseAfterFreeCheck) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; Engine::Config config = { .commandBufferSizeMB = (uint32_t) commandBufferSizeMB, @@ -479,16 +504,35 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu .minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB, .perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB, .jobSystemThreadCount = (uint32_t) jobSystemThreadCount, + .textureUseAfterFreePoolSize = (uint32_t) textureUseAfterFreePoolSize, + .disableParallelShaderCompile = (bool) disableParallelShaderCompile, + .stereoscopicType = (Engine::StereoscopicType) stereoscopicType, + .stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount, + .resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB, + .resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge, + .disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck, }; builder->config(&config); } +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderFeatureLevel( + JNIEnv*, jclass, jlong nativeBuilder, jint ordinal) { + Engine::Builder* builder = (Engine::Builder*) nativeBuilder; + builder->featureLevel((Engine::FeatureLevel)ordinal); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderSharedContext( JNIEnv*, jclass, jlong nativeBuilder, jlong sharedContext) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; 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/cpp/EntityManager.cpp b/android/filament-android/src/main/cpp/EntityManager.cpp index 353dd0aa359..9348e2cca7d 100644 --- a/android/filament-android/src/main/cpp/EntityManager.cpp +++ b/android/filament-android/src/main/cpp/EntityManager.cpp @@ -38,7 +38,7 @@ Java_com_google_android_filament_EntityManager_nCreateArray(JNIEnv* env, jclass, // (which it is), but still. em->create((size_t) n, reinterpret_cast(entities)); - env->ReleaseIntArrayElements(entities_, entities, 0); + env->ReleaseIntArrayElements(entities_, entities, JNI_ABORT); } extern "C" JNIEXPORT jint JNICALL diff --git a/android/filament-android/src/main/cpp/Material.cpp b/android/filament-android/src/main/cpp/Material.cpp index f42119e2dba..7ef3b0b5be2 100644 --- a/android/filament-android/src/main/cpp/Material.cpp +++ b/android/filament-android/src/main/cpp/Material.cpp @@ -19,6 +19,7 @@ #include #include "common/NioUtils.h" +#include "common/CallbackUtils.h" using namespace filament; @@ -105,6 +106,22 @@ Java_com_google_android_filament_Material_nGetRefractionType(JNIEnv*, jclass, return (jint) material->getRefractionType(); } +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_Material_nGetReflectionMode(JNIEnv*, jclass, + jlong nativeMaterial) { + Material* material = (Material*) nativeMaterial; + return (jint) material->getReflectionMode(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_Material_nGetFeatureLevel(JNIEnv*, jclass, + jlong nativeMaterial) { + Material* material = (Material*) nativeMaterial; + return (jint) material->getFeatureLevel(); +} + extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_Material_nGetVertexDomain(JNIEnv*, jclass, @@ -255,3 +272,17 @@ Java_com_google_android_filament_Material_nHasParameter(JNIEnv* env, jclass, env->ReleaseStringUTFChars(name_, name); return (jboolean) hasParameter; } + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_Material_nCompile(JNIEnv *env, jclass clazz, + jlong nativeMaterial, jint priority, jint variants, jobject handler, jobject runnable) { + Material* material = (Material*) nativeMaterial; + JniCallback* jniCallback = JniCallback::make(env, handler, runnable); + material->compile( + (Material::CompilerPriorityQueue) priority, + (UserVariantFilterBit) variants, + jniCallback->getHandler(), [jniCallback](Material*){ + JniCallback::postToJavaAndDestroy(jniCallback); + }); +} diff --git a/android/filament-android/src/main/cpp/MaterialInstance.cpp b/android/filament-android/src/main/cpp/MaterialInstance.cpp index c0b0bcb7168..c5c4ba78fbb 100644 --- a/android/filament-android/src/main/cpp/MaterialInstance.cpp +++ b/android/filament-android/src/main/cpp/MaterialInstance.cpp @@ -205,7 +205,7 @@ Java_com_google_android_filament_MaterialInstance_nSetIntParameterArray(JNIEnv * break; } - env->ReleaseIntArrayElements(v_, v, 0); + env->ReleaseIntArrayElements(v_, v, JNI_ABORT); env->ReleaseStringUTFChars(name_, name); } diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index 2713dffa0b4..4e586179baa 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -104,6 +104,14 @@ Java_com_google_android_filament_RenderableManager_nBuilderGeometry__JIIJJIIII(J (size_t) count); } +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nBuilderGeometryType(JNIEnv*, jclass, + jlong nativeBuilder, int type) { + RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; + builder->geometryType((RenderableManager::Builder::GeometryType)type); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_nBuilderMaterial(JNIEnv*, jclass, diff --git a/android/filament-android/src/main/cpp/Scene.cpp b/android/filament-android/src/main/cpp/Scene.cpp index 25e6be09ba4..bfbc70d5e4c 100644 --- a/android/filament-android/src/main/cpp/Scene.cpp +++ b/android/filament-android/src/main/cpp/Scene.cpp @@ -71,6 +71,13 @@ Java_com_google_android_filament_Scene_nRemoveEntities(JNIEnv *env, jclass type, env->ReleaseIntArrayElements(entities, (jint*) nativeEntities, JNI_ABORT); } +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_Scene_nGetEntityCount(JNIEnv *env, jclass type, + jlong nativeScene) { + Scene* scene = (Scene*) nativeScene; + return (jint) scene->getEntityCount(); +} + extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_Scene_nGetRenderableCount(JNIEnv *env, jclass type, jlong nativeScene) { @@ -91,3 +98,22 @@ Java_com_google_android_filament_Scene_nHasEntity(JNIEnv *env, jclass type, jlon Entity entity = Entity::import(entityId); return (jboolean) scene->hasEntity(entity); } + +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_Scene_nGetEntities(JNIEnv *env, jclass , + jlong nativeScene, jintArray outArray, jint length) { + Scene const* const scene = (Scene*) nativeScene; + if (length < scene->getEntityCount()) { + // should not happen because we already checked on the java side + return JNI_FALSE; + } + jint *out = (jint *) env->GetIntArrayElements(outArray, nullptr); + scene->forEach([out, length, i = 0](Entity entity)mutable { + if (i < length) { // this is just paranoia here + out[i++] = (jint) entity.getId(); + } + }); + env->ReleaseIntArrayElements(outArray, (jint*) out, 0); + return JNI_TRUE; +} diff --git a/android/filament-android/src/main/cpp/SwapChain.cpp b/android/filament-android/src/main/cpp/SwapChain.cpp index 27e006ae87a..e1424595b29 100644 --- a/android/filament-android/src/main/cpp/SwapChain.cpp +++ b/android/filament-android/src/main/cpp/SwapChain.cpp @@ -34,7 +34,15 @@ Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* en } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_SwapChain_nIsSRGBSwapChainSupported(JNIEnv *, jclass, jlong nativeEngine) { +Java_com_google_android_filament_SwapChain_nIsSRGBSwapChainSupported( + JNIEnv *, jclass, jlong nativeEngine) { Engine* engine = (Engine*) nativeEngine; - return (bool)SwapChain::isSRGBSwapChainSupported(*engine); + return (jboolean)SwapChain::isSRGBSwapChainSupported(*engine); +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_SwapChain_nIsProtectedContentSupported( + JNIEnv *, jclass, jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + return (jboolean)SwapChain::isProtectedContentSupported(*engine); } diff --git a/android/filament-android/src/main/cpp/ToneMapper.cpp b/android/filament-android/src/main/cpp/ToneMapper.cpp index 21c7e7ab24d..860b95508bc 100644 --- a/android/filament-android/src/main/cpp/ToneMapper.cpp +++ b/android/filament-android/src/main/cpp/ToneMapper.cpp @@ -47,6 +47,11 @@ Java_com_google_android_filament_ToneMapper_nCreateFilmicToneMapper(JNIEnv*, jcl return (jlong) new FilmicToneMapper(); } +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_ToneMapper_nCreatePBRNeutralToneMapper(JNIEnv*, jclass) { + return (jlong) new PBRNeutralToneMapper(); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) { return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look)); diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index fc4de145e90..e2d8efacb3a 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -480,6 +480,17 @@ Java_com_google_android_filament_View_nIsStencilBufferEnabled(JNIEnv *, jclass, return view->isStencilBufferEnabled(); } +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_View_nSetStereoscopicOptions(JNIEnv *, jclass, jlong nativeView, + jboolean enabled) { + View* view = (View*) nativeView; + View::StereoscopicOptions options { + .enabled = (bool) enabled + }; + view->setStereoscopicOptions(options); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetGuardBandOptions(JNIEnv *, jclass, 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 3423e87d056..97301df9806 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 @@ -111,6 +111,8 @@ public class Engine { private long mNativeObject; + private Config mConfig; + @NonNull private final TransformManager mTransformManager; @NonNull private final LightManager mLightManager; @NonNull private final RenderableManager mRenderableManager; @@ -150,8 +152,20 @@ public enum FeatureLevel { FEATURE_LEVEL_0, /** OpenGL ES 3.0 features (default) */ FEATURE_LEVEL_1, + /** OpenGL ES 3.1 features + 16 textures units + cubemap arrays */ + FEATURE_LEVEL_2, /** OpenGL ES 3.1 features + 31 textures units + cubemap arrays */ - FEATURE_LEVEL_2 + FEATURE_LEVEL_3, + }; + + /** + * The type of technique for stereoscopic rendering + */ + public enum StereoscopicType { + /** Stereoscopic rendering is performed using instanced rendering technique. */ + INSTANCED, + /** Stereoscopic rendering is performed using the multiview feature from the graphics backend. */ + MULTIVIEW, }; /** @@ -161,6 +175,7 @@ public static class Builder { @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) private final BuilderFinalizer mFinalizer; private final long mNativeBuilder; + private Config mConfig; public Builder() { mNativeBuilder = nCreateBuilder(); @@ -202,10 +217,40 @@ public Builder sharedContext(Object sharedContext) { * @return A reference to this Builder for chaining calls. */ public Builder config(Config config) { + mConfig = config; nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB, config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB, config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB, - config.jobSystemThreadCount); + config.jobSystemThreadCount, + config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile, + config.stereoscopicType.ordinal(), config.stereoscopicEyeCount, + config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge, + config.disableHandleUseAfterFreeCheck); + return this; + } + + /** + * Sets the initial featureLevel for the Engine. + * + * @param featureLevel The feature level at which initialize Filament. + * @return A reference to this Builder for chaining calls. + */ + public Builder featureLevel(FeatureLevel featureLevel) { + nSetBuilderFeatureLevel(mNativeBuilder, featureLevel.ordinal()); + return this; + } + + /** + * Sets the initial paused state of the rendering thread. + * + *

Warning: This is an experimental API. See {@link Engine#setPaused(boolean)} for + * caveats. + * + * @param paused Whether to start the rendering thread paused. + * @return A reference to this Builder for chaining calls. + */ + public Builder paused(boolean paused) { + nSetBuilderPaused(mNativeBuilder, paused); return this; } @@ -222,7 +267,7 @@ public Builder config(Config config) { public Engine build() { long nativeEngine = nBuilderBuild(mNativeBuilder); if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine"); - return new Engine(nativeEngine); + return new Engine(nativeEngine, mConfig); } private static class BuilderFinalizer { @@ -330,14 +375,68 @@ public static class Config { * the number of threads to use. */ public long jobSystemThreadCount = 0; + + /** + * Number of most-recently destroyed textures to track for use-after-free. + * + * This will cause the backend to throw an exception when a texture is freed but still bound + * to a SamplerGroup and used in a draw call. 0 disables completely. + * + * Currently only respected by the Metal backend. + */ + public long textureUseAfterFreePoolSize = 0; + + /** + * Set to `true` to forcibly disable parallel shader compilation in the backend. + * Currently only honored by the GL backend. + */ + public boolean disableParallelShaderCompile = false; + + /** + * The type of technique for stereoscopic rendering. + * + * This setting determines the algorithm used when stereoscopic rendering is enabled. This + * decision applies to the entire Engine for the lifetime of the Engine. E.g., multiple + * Views created from the Engine must use the same stereoscopic type. + * + * Each view can enable stereoscopic rendering via the StereoscopicOptions::enable flag. + * + * @see View#setStereoscopicOptions + */ + public StereoscopicType stereoscopicType = StereoscopicType.INSTANCED; + + /** + * The number of eyes to render when stereoscopic rendering is enabled. Supported values are + * between 1 and Engine#getMaxStereoscopicEyes() (inclusive). + * + * @see View#setStereoscopicOptions + * @see Engine#getMaxStereoscopicEyes + */ + public long stereoscopicEyeCount = 2; + + /* + * @Deprecated This value is no longer used. + */ + public long resourceAllocatorCacheSizeMB = 64; + + /* + * This value determines for how many frames are texture entries kept in the cache. + */ + public long resourceAllocatorCacheMaxAge = 2; + + /* + * Disable backend handles use-after-free checks. + */ + public boolean disableHandleUseAfterFreeCheck = false; } - private Engine(long nativeEngine) { + private Engine(long nativeEngine, Config config) { mNativeObject = nativeEngine; mTransformManager = new TransformManager(nGetTransformManager(nativeEngine)); mLightManager = new LightManager(nGetLightManager(nativeEngine)); mRenderableManager = new RenderableManager(nGetRenderableManager(nativeEngine)); mEntityManager = new EntityManager(nGetEntityManager(nativeEngine)); + mConfig = config; } /** @@ -468,17 +567,23 @@ public FeatureLevel getSupportedFeatureLevel() { } /** - * Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is - * active. The selected feature level must not be higher than the value returned by - * getActiveFeatureLevel() and it's not possible lower the active feature level. + * Activate all features of a given feature level. If an explicit feature level is not specified + * at Engine initialization time via {@link Builder#featureLevel}, the default feature level is + * {@link FeatureLevel#FEATURE_LEVEL_0} on devices not compatible with GLES 3.0; otherwise, the + * default is {@link FeatureLevel::FEATURE_LEVEL_1}. The selected feature level must not be + * higher than the value returned by {@link #getActiveFeatureLevel} and it's not possible lower + * the active feature level. Additionally, it is not possible to modify the feature level at all + * if the Engine was initialized at {@link FeatureLevel#FEATURE_LEVEL_0}. * - * @param featureLevel the feature level to activate. If featureLevel is lower than - * getActiveFeatureLevel(), the current (higher) feature level is kept. - * If featureLevel is higher than getSupportedFeatureLevel(), an exception - * is thrown, or the program is terminated if exceptions are disabled. + * @param featureLevel the feature level to activate. If featureLevel is lower than {@link + * #getActiveFeatureLevel}, the current (higher) feature level is kept. If + * featureLevel is higher than {@link #getSupportedFeatureLevel}, or if the + * engine was initialized at feature level 0, an exception is thrown, or the + * program is terminated if exceptions are disabled. * * @return the active feature level. * + * @see Builder#featureLevel * @see #getSupportedFeatureLevel * @see #getActiveFeatureLevel */ @@ -524,6 +629,37 @@ public boolean isAutomaticInstancingEnabled() { return nIsAutomaticInstancingEnabled(getNativeObject()); } + /** + * Retrieves the configuration settings of this {@link Engine}. + * + * This method returns the configuration object that was supplied to the Engine's {@link + * Builder#config} method during the creation of this Engine. If the {@link Builder::config} + * method was not explicitly called (or called with null), this method returns the default + * configuration settings. + * + * @return a {@link Config} object with this Engine's configuration + * @see Builder#config + */ + @NonNull + public Config getConfig() { + if (mConfig == null) { + mConfig = new Config(); + } + return mConfig; + } + + /** + * Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of + * eyes rendered is set at Engine creation time with the {@link + * Engine#Config#stereoscopicEyeCount} setting. + * + * @return the max number of stereoscopic eyes supported + * @see Engine#Config#stereoscopicEyeCount + */ + public long getMaxStereoscopicEyes() { + return nGetMaxStereoscopicEyes(getNativeObject()); + } + // SwapChain @@ -538,7 +674,7 @@ public boolean isAutomaticInstancingEnabled() { */ @NonNull public SwapChain createSwapChain(@NonNull Object surface) { - return createSwapChain(surface, SwapChain.CONFIG_DEFAULT); + return createSwapChain(surface, SwapChainFlags.CONFIG_DEFAULT); } /** @@ -546,15 +682,15 @@ public SwapChain createSwapChain(@NonNull Object surface) { * * @param surface on Android, must be an instance of {@link android.view.Surface} * - * @param flags configuration flags, see {@link SwapChain} + * @param flags configuration flags, see {@link SwapChainFlags} * * @return a newly created {@link SwapChain} object * * @exception IllegalStateException can be thrown if the SwapChain couldn't be created * - * @see SwapChain#CONFIG_DEFAULT - * @see SwapChain#CONFIG_TRANSPARENT - * @see SwapChain#CONFIG_READABLE + * @see SwapChainFlags#CONFIG_DEFAULT + * @see SwapChainFlags#CONFIG_TRANSPARENT + * @see SwapChainFlags#CONFIG_READABLE * */ @NonNull @@ -572,21 +708,22 @@ public SwapChain createSwapChain(@NonNull Object surface, long flags) { * * @param width width of the rendering buffer * @param height height of the rendering buffer - * @param flags configuration flags, see {@link SwapChain} + * @param flags configuration flags, see {@link SwapChainFlags} * * @return a newly created {@link SwapChain} object * * @exception IllegalStateException can be thrown if the SwapChain couldn't be created * - * @see SwapChain#CONFIG_DEFAULT - * @see SwapChain#CONFIG_TRANSPARENT - * @see SwapChain#CONFIG_READABLE + * @see SwapChainFlags#CONFIG_DEFAULT + * @see SwapChainFlags#CONFIG_TRANSPARENT + * @see SwapChainFlags#CONFIG_READABLE * */ @NonNull public SwapChain createSwapChain(int width, int height, long flags) { if (width >= 0 && height >= 0) { - long nativeSwapChain = nCreateSwapChainHeadless(getNativeObject(), width, height, flags); + long nativeSwapChain = + nCreateSwapChainHeadless(getNativeObject(), width, height, flags); if (nativeSwapChain == 0) throw new IllegalStateException("Couldn't create SwapChain"); return new SwapChain(nativeSwapChain, null); } @@ -598,11 +735,12 @@ public SwapChain createSwapChain(int width, int height, long flags) { * * @param surface a properly initialized {@link NativeSurface} * - * @param flags configuration flags, see {@link SwapChain} + * @param flags configuration flags, see {@link SwapChainFlags} * * @return a newly created {@link SwapChain} object * - * @exception IllegalStateException can be thrown if the {@link SwapChain} couldn't be created + * @exception IllegalStateException can be thrown if the {@link SwapChainFlags} couldn't be + * created */ @NonNull public SwapChain createSwapChainFromNativeSurface(@NonNull NativeSurface surface, long flags) { @@ -1060,6 +1198,34 @@ public void flushAndWait() { nFlushAndWait(getNativeObject()); } + /** + * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) but does not wait + * for commands to be either executed or the hardware finished. + * + *

This is typically used after creating a lot of objects to start draining the command + * queue which has a limited size.

+ */ + public void flush() { + nFlush(getNativeObject()); + } + + /** + * Pause or resume the rendering thread. + * + *

Warning: This is an experimental API. In particular, note the following caveats. + * + *

  • + * Buffer callbacks will never be called as long as the rendering thread is paused. + * Do not rely on a buffer callback to unpause the thread. + *
  • + * While the rendering thread is paused, rendering commands will continue to be queued until the + * buffer limit is reached. When the limit is reached, the program will abort. + *
+ */ + public void setPaused(boolean paused) { + nSetPaused(getNativeObject(), paused); + } + @UsedByReflection("TextureHelper.java") public long getNativeObject() { if (mNativeObject == 0) { @@ -1132,6 +1298,8 @@ private static void assertDestroy(boolean success) { private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain); 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); @@ -1139,6 +1307,7 @@ private static void assertDestroy(boolean success) { private static native long nGetEntityManager(long nativeEngine); private static native void nSetAutomaticInstancingEnabled(long nativeEngine, boolean enable); private static native boolean nIsAutomaticInstancingEnabled(long nativeEngine); + private static native long nGetMaxStereoscopicEyes(long nativeEngine); private static native int nGetSupportedFeatureLevel(long nativeEngine); private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal); private static native int nGetActiveFeatureLevel(long nativeEngine); @@ -1148,7 +1317,13 @@ private static void assertDestroy(boolean success) { private static native void nSetBuilderBackend(long nativeBuilder, long backend); private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB, long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB, - long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount); + long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount, + long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile, + int stereoscopicType, long stereoscopicEyeCount, + 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/filament-android/src/main/java/com/google/android/filament/Material.java b/android/filament-android/src/main/java/com/google/android/filament/Material.java index 51d6f01bd54..80b143ee80d 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Material.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Material.java @@ -18,9 +18,11 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.Size; import com.google.android.filament.proguard.UsedByNative; +import com.google.android.filament.Engine.FeatureLevel; import java.nio.Buffer; import java.util.ArrayList; @@ -46,6 +48,8 @@ private EnumCache() { } static final BlendingMode[] sBlendingModeValues = BlendingMode.values(); static final RefractionMode[] sRefractionModeValues = RefractionMode.values(); static final RefractionType[] sRefractionTypeValues = RefractionType.values(); + static final ReflectionMode[] sReflectionModeValues = ReflectionMode.values(); + static final FeatureLevel[] sFeatureLevelValues = FeatureLevel.values(); static final VertexDomain[] sVertexDomainValues = VertexDomain.values(); static final CullingMode[] sCullingModeValues = CullingMode.values(); static final VertexBuffer.VertexAttribute[] sVertexAttributeValues = @@ -181,6 +185,18 @@ public enum RefractionType { THIN } + /** + * Supported reflection modes + * + * @see + * + * Lighting: reflections + */ + public enum ReflectionMode { + DEFAULT, + SCREEN_SPACE + } + /** * Supported types of vertex domains * @@ -223,6 +239,31 @@ public enum CullingMode { FRONT_AND_BACK } + public enum CompilerPriorityQueue { + HIGH, + LOW + } + + public static class UserVariantFilterBit { + /** Directional lighting */ + public static int DIRECTIONAL_LIGHTING = 0x01; + /** Dynamic lighting */ + public static int DYNAMIC_LIGHTING = 0x02; + /** Shadow receiver */ + public static int SHADOW_RECEIVER = 0x04; + /** Skinning */ + public static int SKINNING = 0x08; + /** Fog */ + public static int FOG = 0x10; + /** Variance shadow maps */ + public static int VSM = 0x20; + /** Screen-space reflections */ + public static int SSR = 0x40; + /** Instanced stereo rendering */ + public static int STE = 0x80; + public static int ALL = 0xFF; + } + @UsedByNative("Material.cpp") public static class Parameter { private static final Type[] sTypeValues = Type.values(); @@ -337,6 +378,55 @@ public Material build(@NonNull Engine engine) { } } + + /** + * Asynchronously ensures that a subset of this Material's variants are compiled. After issuing + * several compile() calls in a row, it is recommended to call {@link Engine#flush} + * such that the backend can start the compilation work as soon as possible. + * The provided callback is guaranteed to be called on the main thread after all specified + * variants of the material are compiled. This can take hundreds of milliseconds. + *

+ * If all the material's variants are already compiled, the callback will be scheduled as + * soon as possible, but this might take a few dozen millisecond, corresponding to how + * many previous frames are enqueued in the backend. This also varies by backend. Therefore, + * it is recommended to only call this method once per material shortly after creation. + *

+ *

+ * If the same variant is scheduled for compilation multiple times, the first scheduling + * takes precedence; later scheduling are ignored. + *

+ *

+ * caveat: A consequence is that if a variant is scheduled on the low priority queue and later + * scheduled again on the high priority queue, the later scheduling is ignored. + * Therefore, the second callback could be called before the variant is compiled. + * However, the first callback, if specified, will trigger as expected. + *

+ *

+ * The callback is guaranteed to be called. If the engine is destroyed while some material + * variants are still compiling or in the queue, these will be discarded and the corresponding + * callback will be called. In that case however the Material pointer passed to the callback + * is guaranteed to be invalid (either because it's been destroyed by the user already, or, + * because it's been cleaned-up by the Engine). + *

+ *

+ * {@link UserVariantFilterBit#ALL} should be used with caution. Only variants that an application + * needs should be included in the variants argument. For example, the STE variant is only used + * for stereoscopic rendering. If an application is not planning to render in stereo, this bit + * should be turned off to avoid unnecessary material compilations. + *

+ * @param priority Which priority queue to use, LOW or HIGH. + * @param variants Variants to include to the compile command. + * @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}. + * @param callback callback called on the main thread when the compilation is done on + * by backend. + */ + public void compile(@NonNull CompilerPriorityQueue priority, + int variants, + @Nullable Object handler, + @Nullable Runnable callback) { + nCompile(getNativeObject(), priority.ordinal(), variants, handler, callback); + } + /** * Creates a new instance of this material. Material instances should be freed using * {@link Engine#destroyMaterialInstance(MaterialInstance)}. @@ -437,6 +527,28 @@ public RefractionType getRefractionType() { return EnumCache.sRefractionTypeValues[nGetRefractionType(getNativeObject())]; } + /** + * Returns the reflection mode of this material. + * + * @see + * + * Lighting: reflections + */ + public ReflectionMode getReflectionMode() { + return EnumCache.sReflectionModeValues[nGetReflectionMode(getNativeObject())]; + } + + /** + * Returns the minimum required feature level for this material. + * + * @see + * + * General: featureLevel + */ + public FeatureLevel getFeatureLevel() { + return EnumCache.sFeatureLevelValues[nGetFeatureLevel(getNativeObject())]; + } + /** * Returns the vertex domain of this material. * @@ -916,6 +1028,7 @@ void clearNativeObject() { private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name); private static native long nGetDefaultInstance(long nativeMaterial); + private static native void nCompile(long nativeMaterial, int priority, int variants, Object handler, Runnable runnable); private static native String nGetName(long nativeMaterial); private static native int nGetShading(long nativeMaterial); private static native int nGetInterpolation(long nativeMaterial); @@ -932,6 +1045,8 @@ void clearNativeObject() { private static native float nGetSpecularAntiAliasingThreshold(long nativeMaterial); private static native int nGetRefractionMode(long nativeMaterial); private static native int nGetRefractionType(long nativeMaterial); + private static native int nGetReflectionMode(long nativeMaterial); + private static native int nGetFeatureLevel(long nativeMaterial); private static native int nGetParameterCount(long nativeMaterial); diff --git a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java index a3c8f1ff1c2..f7a6319d7a8 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java @@ -175,6 +175,32 @@ public Builder geometry(@IntRange(from = 0) int index, @NonNull PrimitiveType ty return this; } + /** + * Type of geometry for a Renderable + */ + public enum GeometryType { + /** dynamic gemoetry has no restriction */ + DYNAMIC, + /** bounds and world space transform are immutable */ + STATIC_BOUNDS, + /** skinning/morphing not allowed and Vertex/IndexBuffer immutables */ + STATIC + } + + /** + * Specify whether this renderable has static bounds. In this context his means that + * the renderable's bounding box cannot change and that the renderable's transform is + * assumed immutable. Changing the renderable's transform via the TransformManager + * can lead to corrupted graphics. Note that skinning and morphing are not forbidden. + * Disabled by default. + * @param enable whether this renderable has static bounds. false by default. + */ + @NonNull + public Builder geometryType(GeometryType type) { + nBuilderGeometryType(mNativeBuilder, type.ordinal()); + return this; + } + /** * Binds a material instance to the specified primitive. * @@ -964,6 +990,7 @@ public long getNativeObject() { private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer); private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count); private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int minIndex, int maxIndex, int count); + private static native void nBuilderGeometryType(long nativeBuilder, int type); private static native void nBuilderMaterial(long nativeBuilder, int index, long nativeMaterialInstance); private static native void nBuilderBlendOrder(long nativeBuilder, int index, int blendOrder); private static native void nBuilderGlobalBlendOrderEnabled(long nativeBuilder, int index, boolean enabled); diff --git a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java index 3a9b6ab94db..ab70e9ed385 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java @@ -101,7 +101,7 @@ public static class FrameRateOptions { /** * Desired frame interval in unit of 1 / DisplayInfo.refreshRate. */ - public float interval = 1.0f / 60.0f; + public float interval = 1.0f; /** * Additional headroom for the GPU as a ratio of the targetFrameTime. diff --git a/android/filament-android/src/main/java/com/google/android/filament/Scene.java b/android/filament-android/src/main/java/com/google/android/filament/Scene.java index 283b7024e73..9a41a4f64e5 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Scene.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Scene.java @@ -16,6 +16,7 @@ package com.google.android.filament; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** @@ -146,18 +147,29 @@ public void removeEntities(@Entity int[] entities) { } /** - * Returns the number of {@link RenderableManager} components in the Scene. + * Returns the total number of Entities in the Scene, whether alive or not. * - * @return number of {@link RenderableManager} components in the Scene.. + * @return the total number of Entities in the Scene. + */ + public int getEntityCount() { + return nGetEntityCount(getNativeObject()); + } + + /** + * Returns the number of active (alive) {@link RenderableManager} components in the + * Scene. + * + * @return number of {@link RenderableManager} components in the Scene. */ public int getRenderableCount() { return nGetRenderableCount(getNativeObject()); } /** - * Returns the number of {@link LightManager} components in the Scene. + * Returns the number of active (alive) {@link LightManager} components in the + * Scene. * - * @return number of {@link LightManager} components in the Scene.. + * @return number of {@link LightManager} components in the Scene. */ public int getLightCount() { return nGetLightCount(getNativeObject()); @@ -179,6 +191,52 @@ public long getNativeObject() { return mNativeObject; } + /** + * Returns the list of all entities in the Scene. If outArray is provided and large enough, + * it is used to store the list and returned, otherwise a new array is allocated and returned. + * @param outArray an array to store the list of entities in the scene. + * @return outArray if it was used or a newly allocated array. + * @see #getEntityCount + */ + public int[] getEntities(@Nullable int[] outArray) { + int c = getEntityCount(); + if (outArray == null || outArray.length < c) { + outArray = new int[c]; + } + boolean success = nGetEntities(getNativeObject(), outArray, outArray.length); + if (!success) { + throw new IllegalStateException("Error retriving Scene's entities"); + } + return outArray; + } + + /** + * Returns the list of all entities in the Scene in a newly allocated array. + * @return an array containing the list of all entities in the scene. + * @see #getEntityCount + */ + public int[] getEntities() { + return getEntities(null); + } + + public interface EntityProcessor { + void process(@Entity int entity); + } + + /** + * Invokes user functor on each entity in the scene. + * + * It is not allowed to add or remove an entity from the scene within the functor. + * + * @param entityProcessor User provided functor called for each entity in the scene + */ + public void forEach(@NonNull EntityProcessor entityProcessor) { + int[] entities = getEntities(null); + for (int entity : entities) { + entityProcessor.process(entity); + } + } + void clearNativeObject() { mNativeObject = 0; } @@ -189,7 +247,9 @@ void clearNativeObject() { private static native void nAddEntities(long nativeScene, int[] entities); private static native void nRemove(long nativeScene, int entity); private static native void nRemoveEntities(long nativeScene, int[] entities); + private static native int nGetEntityCount(long nativeScene); private static native int nGetRenderableCount(long nativeScene); private static native int nGetLightCount(long nativeScene); private static native boolean nHasEntity(long nativeScene, int entity); + private static native boolean nGetEntities(long nativeScene, int[] outArray, int length); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java index 429af3e140d..db47d215635 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java +++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java @@ -68,77 +68,30 @@ public class SwapChain { private final Object mSurface; private long mNativeObject; - public static final long CONFIG_DEFAULT = 0x0; - - /** - * This flag indicates that the SwapChain must be allocated with an - * alpha-channel. - */ - public static final long CONFIG_TRANSPARENT = 0x1; - - /** - * This flag indicates that the SwapChain may be used as a source surface - * for reading back render results. This config must be set when creating - * any SwapChain that will be used as the source for a blit operation. - * - * @see Renderer#copyFrame - */ - public static final long CONFIG_READABLE = 0x2; - - /** - * Indicates that the native X11 window is an XCB window rather than an XLIB window. - * This is ignored on non-Linux platforms and in builds that support only one X11 API. - */ - public static final long CONFIG_ENABLE_XCB = 0x4; - - /** - * Indicates that the SwapChain must automatically perform linear to sRGB encoding. - * - * This flag is ignored if isSRGBSwapChainSupported() is false. - * - * When using this flag, post-processing should be disabled. - * - * @see SwapChain#isSRGBSwapChainSupported - * @see View#setPostProcessingEnabled - */ - public static final long CONFIG_SRGB_COLORSPACE = 0x10; + SwapChain(long nativeSwapChain, Object surface) { + mNativeObject = nativeSwapChain; + mSurface = surface; + } /** - * Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer. - * - * This flag is necessary when using View::setStencilBufferEnabled and rendering directly into - * the SwapChain (when post-processing is disabled). - * - * The specific format of the stencil buffer depends on platform support. The following pixel - * formats are tried, in order of preference: - * - * Depth only (without CONFIG_HAS_STENCIL_BUFFER): - * - DEPTH32F - * - DEPTH24 - * - * Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER): - * - DEPTH32F_STENCIL8 - * - DEPTH24F_STENCIL8 - * - * Note that enabling the stencil buffer may hinder depth precision and should only be used if - * necessary. + * Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag. + * The default implementation returns false. * - * @see View#setStencilBufferEnabled - * @see View#setPostProcessingEnabled + * @param engine A reference to the filament Engine + * @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise. + * @see SwapChainFlags#CONFIG_PROTECTED_CONTENT */ - public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20; - - SwapChain(long nativeSwapChain, Object surface) { - mNativeObject = nativeSwapChain; - mSurface = surface; + public static boolean isProtectedContentSupported(@NonNull Engine engine) { + return nIsProtectedContentSupported(engine.getNativeObject()); } /** - * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. + * Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag. * The default implementation returns false. * * @param engine A reference to the filament Engine - * @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise. + * @return true if CONFIG_SRGB_COLORSPACE is supported, false otherwise. + * @see SwapChainFlags#CONFIG_SRGB_COLORSPACE */ public static boolean isSRGBSwapChainSupported(@NonNull Engine engine) { return nIsSRGBSwapChainSupported(engine.getNativeObject()); @@ -186,4 +139,5 @@ void clearNativeObject() { private static native void nSetFrameCompletedCallback(long nativeSwapChain, Object handler, Runnable callback); private static native boolean nIsSRGBSwapChainSupported(long nativeEngine); + private static native boolean nIsProtectedContentSupported(long nativeEngine); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java new file mode 100644 index 00000000000..daef2dd15f2 --- /dev/null +++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.filament; + +// Note: SwapChainFlags is kept separate from SwapChain so that UiHelper does not need to depend +// on SwapChain. This allows clients to use UiHelper without requiring all of Filament's Java +// classes. + +/** + * Flags that a SwapChain can be created with to control behavior. + * + * @see Engine#createSwapChain + * @see Engine#createSwapChainFromNativeSurface + */ +public final class SwapChainFlags { + + public static final long CONFIG_DEFAULT = 0x0; + + /** + * This flag indicates that the SwapChain must be allocated with an + * alpha-channel. + */ + public static final long CONFIG_TRANSPARENT = 0x1; + + /** + * This flag indicates that the SwapChain may be used as a source surface + * for reading back render results. This config must be set when creating + * any SwapChain that will be used as the source for a blit operation. + * + * @see Renderer#copyFrame + */ + public static final long CONFIG_READABLE = 0x2; + + /** + * Indicates that the native X11 window is an XCB window rather than an XLIB window. + * This is ignored on non-Linux platforms and in builds that support only one X11 API. + */ + public static final long CONFIG_ENABLE_XCB = 0x4; + + /** + * Indicates that the SwapChain must automatically perform linear to sRGB encoding. + * + * This flag is ignored if isSRGBSwapChainSupported() is false. + * + * When using this flag, post-processing should be disabled. + * + * @see SwapChain#isSRGBSwapChainSupported + * @see View#setPostProcessingEnabled + */ + public static final long CONFIG_SRGB_COLORSPACE = 0x10; + + /** + * Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer. + * + * This flag is necessary when using View::setStencilBufferEnabled and rendering directly into + * the SwapChain (when post-processing is disabled). + * + * The specific format of the stencil buffer depends on platform support. The following pixel + * formats are tried, in order of preference: + * + * Depth only (without CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F + * - DEPTH24 + * + * Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F_STENCIL8 + * - DEPTH24F_STENCIL8 + * + * Note that enabling the stencil buffer may hinder depth precision and should only be used if + * necessary. + * + * @see View#setStencilBufferEnabled + * @see View#setPostProcessingEnabled + */ + public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20; + + /** + * The SwapChain contains protected content. Only supported when isProtectedContentSupported() + * is true. + */ + public static final long CONFIG_PROTECTED_CONTENT = 0x40; +} + diff --git a/android/filament-android/src/main/java/com/google/android/filament/Texture.java b/android/filament-android/src/main/java/com/google/android/filament/Texture.java index db312a93442..1cb732af13f 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Texture.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Texture.java @@ -849,6 +849,10 @@ public static class Usage { public static final int SAMPLEABLE = 0x10; /** Texture can be used as a subpass input */ public static final int SUBPASS_INPUT = 0x20; + /** Texture can be used the source of a blit() */ + public static final int BLIT_SRC = 0x40; + /** Texture can be used the destination of a blit() */ + public static final int BLIT_DST = 0x80; /** by default textures are UPLOADABLE and SAMPLEABLE*/ public static final int DEFAULT = UPLOADABLE | SAMPLEABLE; } diff --git a/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java b/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java index 58ffc501ec1..c3d3490441b 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java +++ b/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java @@ -16,12 +16,14 @@ *
  • Configurable tone mapping operators
  • *
      *
    • GenericToneMapper
    • + *
    • AgXToneMapper
    • *
    *
  • Fixed-aesthetic tone mapping operators
  • *
      *
    • ACESToneMapper
    • *
    • ACESLegacyToneMapper
    • *
    • FilmicToneMapper
    • + *
    • PBRNeutralToneMapper
    • *
    *
  • Debug/validation tone mapping operators
  • *
      @@ -100,11 +102,21 @@ public Filmic() { } } + /** + * Khronos PBR Neutral tone mapping operator. This tone mapper was designed + * to preserve the appearance of materials across lighting conditions while + * avoiding artifacts in the highlights in high dynamic range conditions. + */ + public static class PBRNeutralToneMapper extends ToneMapper { + public PBRNeutralToneMapper() { + super(nCreatePBRNeutralToneMapper()); + } + } + /** * AgX tone mapping operator. */ public static class Agx extends ToneMapper { - public enum AgxLook { /** * Base contrast with no look applied @@ -233,6 +245,7 @@ public void setHdrMax(float hdrMax) { private static native long nCreateACESToneMapper(); private static native long nCreateACESLegacyToneMapper(); private static native long nCreateFilmicToneMapper(); + private static native long nCreatePBRNeutralToneMapper(); private static native long nCreateAgxToneMapper(int look); private static native long nCreateGenericToneMapper( float contrast, float midGrayIn, float midGrayOut, float hdrMax); diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index 91622d26c41..28cb268c233 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -75,6 +75,7 @@ public class View { private AmbientOcclusionOptions mAmbientOcclusionOptions; private BloomOptions mBloomOptions; private FogOptions mFogOptions; + private StereoscopicOptions mStereoscopicOptions; private RenderTarget mRenderTarget; private BlendMode mBlendMode; private DepthOfFieldOptions mDepthOfFieldOptions; @@ -1055,6 +1056,51 @@ public boolean isStencilBufferEnabled() { return nIsStencilBufferEnabled(getNativeObject()); } + /** + * Sets the stereoscopic rendering options for this view. + * + *

      + * Currently, only one type of stereoscopic rendering is supported: side-by-side. + * Side-by-side stereo rendering splits the viewport into two halves: a left and right half. + * Eye 0 will render to the left half, while Eye 1 will render into the right half. + *

      + * + *

      + * Currently, the following features are not supported with stereoscopic rendering: + * - post-processing + * - shadowing + * - punctual lights + *

      + * + *

      + * Stereo rendering depends on device and platform support. To check if stereo rendering is + * supported, use {@link Engine#isStereoSupported()}. If stereo rendering is not supported, then + * the stereoscopic options have no effect. + *

      + * + * @param options The stereoscopic options to use on this view + * @see #getStereoscopicOptions + */ + public void setStereoscopicOptions(@NonNull StereoscopicOptions options) { + mStereoscopicOptions = options; + nSetStereoscopicOptions(getNativeObject(), options.enabled); + } + + /** + * Gets the stereoscopic options. + * + * @return options Stereoscopic options currently set. + * @see #setStereoscopicOptions + */ + @NonNull + public StereoscopicOptions getStereoscopicOptions() { + if (mStereoscopicOptions == null) { + mStereoscopicOptions = new StereoscopicOptions(); + } + return mStereoscopicOptions; + } + + /** * A class containing the result of a picking query */ @@ -1220,6 +1266,7 @@ void clearNativeObject() { private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, int levels, int blendMode, boolean threshold, boolean enabled, float highlight, boolean lensFlare, boolean starburst, float chromaticAberration, int ghostCount, float ghostSpacing, float ghostThreshold, float haloThickness, float haloRadius, float haloThreshold); private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float cutOffDistance, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, long skyColorNativeObject, boolean enabled); + private static native void nSetStereoscopicOptions(long nativeView, boolean enabled); private static native void nSetBlendMode(long nativeView, int blendMode); private static native void nSetDepthOfFieldOptions(long nativeView, float cocScale, float maxApertureDiameter, boolean enabled, int filter, boolean nativeResolution, int foregroundRingCount, int backgroundRingCount, int fastGatherRingCount, int maxForegroundCOC, int maxBackgroundCOC); @@ -1590,6 +1637,10 @@ public enum Filter { * circle of confusion scale factor (amount of blur) */ public float cocScale = 1.0f; + /** + * width/height aspect ratio of the circle of confusion (simulate anamorphic lenses) + */ + public float cocAspectRatio = 1.0f; /** * maximum aperture diameter in meters (zero to disable rotation) */ @@ -1805,7 +1856,7 @@ public static class AmbientOcclusionOptions { } /** - * Options for Temporal Multi-Sample Anti-aliasing (MSAA) + * Options for Multi-Sample Anti-aliasing (MSAA) * @see setMultiSampleAntiAliasingOptions() */ public static class MultiSampleAntiAliasingOptions { @@ -1829,21 +1880,111 @@ public static class MultiSampleAntiAliasingOptions { /** * Options for Temporal Anti-aliasing (TAA) + * Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process + * shaders to be recompiled. These options should be changed or set during initialization. + * `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time. + * + * `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state. + * see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information. + * * @see setTemporalAntiAliasingOptions() */ public static class TemporalAntiAliasingOptions { + public enum BoxType { + /** + * use an AABB neighborhood + */ + AABB, + /** + * use the variance of the neighborhood (not recommended) + */ + VARIANCE, + /** + * use both AABB and variance + */ + AABB_VARIANCE, + } + + public enum BoxClipping { + /** + * Accurate box clipping + */ + ACCURATE, + /** + * clamping + */ + CLAMP, + /** + * no rejections (use for debugging) + */ + NONE, + } + + public enum JitterPattern { + RGSS_X4, + UNIFORM_HELIX_X4, + HALTON_23_X8, + HALTON_23_X16, + HALTON_23_X32, + } + /** - * reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother) + * reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother) */ public float filterWidth = 1.0f; /** * history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). */ - public float feedback = 0.04f; + public float feedback = 0.12f; + /** + * texturing lod bias (typically -1 or -2) + */ + public float lodBias = -1.0f; + /** + * post-TAA sharpen, especially useful when upscaling is true. + */ + public float sharpness = 0.0f; /** * enables or disables temporal anti-aliasing */ public boolean enabled = false; + /** + * 4x TAA upscaling. Disables Dynamic Resolution. [BETA] + */ + public boolean upscaling = false; + /** + * whether to filter the history buffer + */ + public boolean filterHistory = true; + /** + * whether to apply the reconstruction filter to the input + */ + public boolean filterInput = true; + /** + * whether to use the YcoCg color-space for history rejection + */ + public boolean useYCoCg = false; + /** + * type of color gamut box + */ + @NonNull + public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB; + /** + * clipping algorithm + */ + @NonNull + public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE; + @NonNull + public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16; + public float varianceGamma = 1.0f; + /** + * adjust the feedback dynamically to reduce flickering + */ + public boolean preventFlickering = false; + /** + * whether to apply history reprojection (debug option) + */ + public boolean historyReprojection = true; } /** diff --git a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java index 0b0ff807460..4d6f3a140f1 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java +++ b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java @@ -27,7 +27,7 @@ import android.view.SurfaceView; import android.view.TextureView; -import com.google.android.filament.SwapChain; +import com.google.android.filament.SwapChainFlags; /** * UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder @@ -182,28 +182,85 @@ private interface RenderSurface { void detach(); } - private static class SurfaceViewHandler implements RenderSurface { - private final SurfaceView mSurfaceView; + private class SurfaceViewHandler implements RenderSurface, SurfaceHolder.Callback { + @NonNull private final SurfaceView mSurfaceView; - SurfaceViewHandler(SurfaceView surface) { - mSurfaceView = surface; + SurfaceViewHandler(@NonNull SurfaceView surfaceView) { + mSurfaceView = surfaceView; + + @NonNull SurfaceHolder holder = surfaceView.getHolder(); + holder.addCallback(this); + + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + holder.setFixedSize(mDesiredWidth, mDesiredHeight); + } + + // in case the SurfaceView's surface already existed + final Surface surface = holder.getSurface(); + if (surface != null && surface.isValid()) { + surfaceCreated(holder); + // there is no way to retrieve the actual PixelFormat, since it is not used + // in the callback, we can use whatever we want. + surfaceChanged(holder, PixelFormat.RGBA_8888, + holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); + } } @Override public void resize(int width, int height) { - mSurfaceView.getHolder().setFixedSize(width, height); + @NonNull SurfaceHolder holder = mSurfaceView.getHolder(); + holder.setFixedSize(width, height); } @Override public void detach() { + @NonNull SurfaceHolder holder = mSurfaceView.getHolder(); + holder.removeCallback(this); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); + createSwapChain(holder.getSurface()); + } + + @Override + public void surfaceChanged( + @NonNull SurfaceHolder holder, int format, int width, int height) { + // Note: this is always called at least once after surfaceCreated() + if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); + if (mRenderCallback != null) { + mRenderCallback.onResized(width, height); + } + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); + destroySwapChain(); } } - private static class SurfaceHolderHandler implements RenderSurface { + private class SurfaceHolderHandler implements RenderSurface, SurfaceHolder.Callback { private final SurfaceHolder mSurfaceHolder; - SurfaceHolderHandler(SurfaceHolder surface) { - mSurfaceHolder = surface; + SurfaceHolderHandler(@NonNull SurfaceHolder holder) { + mSurfaceHolder = holder; + holder.addCallback(this); + + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + holder.setFixedSize(mDesiredWidth, mDesiredHeight); + } + + // in case the SurfaceHolder's surface already existed + final Surface surface = holder.getSurface(); + if (surface != null && surface.isValid()) { + surfaceCreated(holder); + // there is no way to retrieve the actual PixelFormat, since it is not used + // in the callback, we can use whatever we want. + surfaceChanged(holder, PixelFormat.RGBA_8888, + holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); + } } @Override @@ -213,30 +270,127 @@ public void resize(int width, int height) { @Override public void detach() { + mSurfaceHolder.removeCallback(this); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); + createSwapChain(holder.getSurface()); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { + // Note: this is always called at least once after surfaceCreated() + if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); + if (mRenderCallback != null) { + mRenderCallback.onResized(width, height); + } + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) { + if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); + destroySwapChain(); } } - private class TextureViewHandler implements RenderSurface { + private class TextureViewHandler implements RenderSurface, TextureView.SurfaceTextureListener { private final TextureView mTextureView; private Surface mSurface; - TextureViewHandler(TextureView surface) { mTextureView = surface; } + TextureViewHandler(@NonNull TextureView view) { + mTextureView = view; + mTextureView.setSurfaceTextureListener(this); + // in case the View's SurfaceTexture already existed + if (view.isAvailable()) { + SurfaceTexture surfaceTexture = view.getSurfaceTexture(); + if (surfaceTexture != null) { + this.onSurfaceTextureAvailable(surfaceTexture, + mDesiredWidth, mDesiredHeight); + } + } + } @Override public void resize(int width, int height) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); + final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); + if (surfaceTexture != null) { + surfaceTexture.setDefaultBufferSize(width, height); + } + } + if (mRenderCallback != null) { + // the call above won't cause TextureView.onSurfaceTextureSizeChanged() + mRenderCallback.onResized(width, height); } - // the call above won't cause TextureView.onSurfaceTextureSizeChanged() - mRenderCallback.onResized(width, height); } @Override public void detach() { + mTextureView.setSurfaceTextureListener(null); + } + + + @Override + public void onSurfaceTextureAvailable( + @NonNull SurfaceTexture surfaceTexture, int width, int height) { + if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); + } + } + + final Surface surface = new Surface(surfaceTexture); + setSurface(surface); + createSwapChain(surface); + + if (mRenderCallback != null) { + // Call this the first time because onSurfaceTextureSizeChanged() + // isn't called at initialization time + mRenderCallback.onResized(width, height); + } + } + + @Override + public void onSurfaceTextureSizeChanged( + @NonNull SurfaceTexture surfaceTexture, int width, int height) { + if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()"); + if (mRenderCallback != null) { + if (mDesiredWidth > 0 && mDesiredHeight > 0) { + surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); + mRenderCallback.onResized(mDesiredWidth, mDesiredHeight); + } else { + mRenderCallback.onResized(width, height); + } + // We must recreate the SwapChain to guarantee that it sees the new size. + // More precisely, for an EGL client, the EGLSurface must be recreated. For + // a Vulkan client, the SwapChain must be recreated. Calling + // onNativeWindowChanged() will accomplish that. + // This requirement comes from SurfaceTexture.setDefaultBufferSize() + // documentation. + final Surface surface = getSurface(); + if (surface != null) { + mRenderCallback.onNativeWindowChanged(surface); + } + } + } + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) { + if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()"); setSurface(null); + destroySwapChain(); + return true; } - void setSurface(Surface surface) { + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } + + + private void setSurface(@Nullable Surface surface) { if (surface == null) { if (mSurface != null) { mSurface.release(); @@ -245,7 +399,7 @@ void setSurface(Surface surface) { mSurface = surface; } - public Surface getSurface() { + private Surface getSurface() { return mSurface; } } @@ -291,6 +445,9 @@ public RendererCallback getRenderCallback() { * {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}. */ public void detach() { + if (mRenderSurface != null) { + mRenderSurface.detach(); + } destroySwapChain(); mNativeWindow = null; mRenderSurface = null; @@ -298,7 +455,6 @@ public void detach() { /** * Checks whether we are ready to render into the attached surface. - * * Using OpenGL ES when this returns true, will result in drawing commands being lost, * HOWEVER, GLES state will be preserved. This is useful to initialize the engine. * @@ -343,7 +499,6 @@ public boolean isOpaque() { /** * Controls whether the render target (SurfaceView or TextureView) is opaque or not. * The render target is considered opaque by default. - * * Must be called before calling {@link #attachTo(SurfaceView)}, {@link #attachTo(TextureView)}, * or {@link #attachTo(SurfaceHolder)}. * @@ -366,10 +521,8 @@ public boolean isMediaOverlay() { * positioned above other surfaces but below the activity's surface. This property * only has an effect when used in combination with {@link #setOpaque(boolean) setOpaque(false)} * and does not affect TextureView targets. - * * Must be called before calling {@link #attachTo(SurfaceView)} * or {@link #attachTo(TextureView)}. - * * Has no effect when using {@link #attachTo(SurfaceHolder)}. * * @param overlay Indicates whether the render target should be rendered below the activity's @@ -385,12 +538,11 @@ public void setMediaOverlay(boolean overlay) { * the options set on this UiHelper. */ public long getSwapChainFlags() { - return isOpaque() ? SwapChain.CONFIG_DEFAULT : SwapChain.CONFIG_TRANSPARENT; + return isOpaque() ? SwapChainFlags.CONFIG_DEFAULT : SwapChainFlags.CONFIG_TRANSPARENT; } /** * Associate UiHelper with a SurfaceView. - * * As soon as SurfaceView is ready (i.e. has a Surface), we'll create the * EGL resources needed, and call user callbacks if needed. */ @@ -405,171 +557,32 @@ public void attachTo(@NonNull SurfaceView view) { view.setZOrderOnTop(translucent); } - int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; - view.getHolder().setFormat(format); - + view.getHolder().setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT); mRenderSurface = new SurfaceViewHandler(view); - - final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); - createSwapChain(holder.getSurface()); - } - - @Override - public void surfaceChanged( - SurfaceHolder holder, int format, int width, int height) { - // Note: this is always called at least once after surfaceCreated() - if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); - mRenderCallback.onResized(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); - destroySwapChain(); - } - }; - - SurfaceHolder holder = view.getHolder(); - holder.addCallback(callback); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - holder.setFixedSize(mDesiredWidth, mDesiredHeight); - } - - // in case the SurfaceView's surface already existed - final Surface surface = holder.getSurface(); - if (surface != null && surface.isValid()) { - callback.surfaceCreated(holder); - callback.surfaceChanged(holder, format, - holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); - } } } /** * Associate UiHelper with a TextureView. - * * As soon as TextureView is ready (i.e. has a buffer), we'll create the * EGL resources needed, and call user callbacks if needed. */ public void attachTo(@NonNull TextureView view) { if (attach(view)) { view.setOpaque(isOpaque()); - mRenderSurface = new TextureViewHandler(view); - - TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable( - SurfaceTexture surfaceTexture, int width, int height) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); - } - } - - Surface surface = new Surface(surfaceTexture); - TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface; - textureViewHandler.setSurface(surface); - - createSwapChain(surface); - - // Call this the first time because onSurfaceTextureSizeChanged() - // isn't called at initialization time - mRenderCallback.onResized(width, height); - } - - @Override - public void onSurfaceTextureSizeChanged( - SurfaceTexture surfaceTexture, int width, int height) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()"); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight); - mRenderCallback.onResized(mDesiredWidth, mDesiredHeight); - } else { - mRenderCallback.onResized(width, height); - } - // We must recreate the SwapChain to guarantee that it sees the new size. - // More precisely, for an EGL client, the EGLSurface must be recreated. For - // a Vulkan client, the SwapChain must be recreated. Calling - // onNativeWindowChanged() will accomplish that. - // This requirement comes from SurfaceTexture.setDefaultBufferSize() - // documentation. - TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface; - mRenderCallback.onNativeWindowChanged(textureViewHandler.getSurface()); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { - if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()"); - destroySwapChain(); - return true; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { } - }; - - view.setSurfaceTextureListener(listener); - - // in case the View's SurfaceTexture already existed - if (view.isAvailable()) { - SurfaceTexture surfaceTexture = view.getSurfaceTexture(); - listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight); - } } } /** * Associate UiHelper with a SurfaceHolder. - * * As soon as a Surface is created, we'll create the * EGL resources needed, and call user callbacks if needed. */ public void attachTo(@NonNull SurfaceHolder holder) { if (attach(holder)) { - int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; - holder.setFormat(format); - + holder.setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT); mRenderSurface = new SurfaceHolderHandler(holder); - - final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder surfaceHolder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()"); - createSwapChain(holder.getSurface()); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - // Note: this is always called at least once after surfaceCreated() - if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")"); - mRenderCallback.onResized(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()"); - destroySwapChain(); - } - }; - - holder.addCallback(callback); - if (mDesiredWidth > 0 && mDesiredHeight > 0) { - holder.setFixedSize(mDesiredWidth, mDesiredHeight); - } - - // in case the SurfaceHolder's surface already existed - final Surface surface = holder.getSurface(); - if (surface != null && surface.isValid()) { - callback.surfaceCreated(holder); - callback.surfaceChanged(holder, format, - holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height()); - } } } @@ -580,6 +593,10 @@ private boolean attach(@NonNull Object nativeWindow) { // nothing to do return false; } + if (mRenderSurface != null) { + mRenderSurface.detach(); + mRenderSurface = null; + } destroySwapChain(); } mNativeWindow = nativeWindow; @@ -587,15 +604,16 @@ private boolean attach(@NonNull Object nativeWindow) { } private void createSwapChain(@NonNull Surface surface) { - mRenderCallback.onNativeWindowChanged(surface); + if (mRenderCallback != null) { + mRenderCallback.onNativeWindowChanged(surface); + } mHasSwapChain = true; } private void destroySwapChain() { - if (mRenderSurface != null) { - mRenderSurface.detach(); + if (mRenderCallback != null) { + mRenderCallback.onDetachedFromSurface(); } - mRenderCallback.onDetachedFromSurface(); mHasSwapChain = false; } } 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/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java index 71d286cda88..9998ee0cacb 100644 --- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java +++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentInstance.java @@ -144,7 +144,7 @@ public int getJointCountAt(@IntRange(from = 0) int skinIndex) { * * Ignored if variantIndex is out of bounds. */ - void applyMaterialVariant(@IntRange(from = 0) int variantIndex) { + public void applyMaterialVariant(@IntRange(from = 0) int variantIndex) { nApplyMaterialVariant(mNativeObject, variantIndex); } diff --git a/android/gradle.properties b/android/gradle.properties index 9437d5af284..187e11e9c1f 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.44.0 +VERSION_NAME=1.51.3 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 6cbc1d9208a..ad7df40dfff 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Nov 17 10:40:18 PST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/samples/sample-gltf-viewer/build.gradle b/android/samples/sample-gltf-viewer/build.gradle index f934f01901b..c2d06990176 100644 --- a/android/samples/sample-gltf-viewer/build.gradle +++ b/android/samples/sample-gltf-viewer/build.gradle @@ -30,7 +30,7 @@ android { compileSdkVersion versions.compileSdk defaultConfig { applicationId "com.google.android.filament.gltf" - minSdkVersion 19 + minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk } diff --git a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt index 372cba57004..102c4944e6e 100644 --- a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt +++ b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt @@ -26,6 +26,7 @@ import android.widget.TextView import android.widget.Toast import com.google.android.filament.Fence import com.google.android.filament.IndirectLight +import com.google.android.filament.Material import com.google.android.filament.Skybox import com.google.android.filament.View import com.google.android.filament.View.OnPickCallback @@ -392,6 +393,36 @@ class MainActivity : Activity() { Log.i(TAG, "The Filament backend took $total ms to load the model geometry.") modelViewer.engine.destroyFence(it) loadStartFence = null + + val materials = mutableSetOf() + val rcm = modelViewer.engine.renderableManager + modelViewer.scene.forEach { + val entity = it + if (rcm.hasComponent(entity)) { + val ri = rcm.getInstance(entity) + val c = rcm.getPrimitiveCount(ri) + for (i in 0 until c) { + val mi = rcm.getMaterialInstanceAt(ri, i) + val ma = mi.material + materials.add(ma) + } + } + } + materials.forEach { + it.compile( + Material.CompilerPriorityQueue.HIGH, + Material.UserVariantFilterBit.DIRECTIONAL_LIGHTING or + Material.UserVariantFilterBit.DYNAMIC_LIGHTING or + Material.UserVariantFilterBit.SHADOW_RECEIVER, + null, null) + it.compile( + Material.CompilerPriorityQueue.LOW, + Material.UserVariantFilterBit.FOG or + Material.UserVariantFilterBit.SKINNING or + Material.UserVariantFilterBit.SSR or + Material.UserVariantFilterBit.VSM, + null, null) + } } } diff --git a/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt b/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt index 7d34a3031d6..f4b1e868178 100644 --- a/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt +++ b/android/samples/sample-hello-triangle/src/main/java/com/google/android/filament/hellotriangle/MainActivity.kt @@ -20,6 +20,8 @@ import android.animation.ValueAnimator import android.app.Activity import android.opengl.Matrix import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.Choreographer import android.view.Surface import android.view.SurfaceView @@ -110,7 +112,7 @@ class MainActivity : Activity() { } private fun setupFilament() { - engine = Engine.create() + engine = Engine.Builder().featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build() renderer = engine.createRenderer() scene = engine.createScene() view = engine.createView() @@ -120,13 +122,8 @@ class MainActivity : Activity() { private fun setupView() { scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine) - if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) { - // post-processing is not supported at feature level 0 - view.isPostProcessingEnabled = false - } else { - // NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference - // view.isPostProcessingEnabled = false - } + // post-processing is not supported at feature level 0 + view.isPostProcessingEnabled = false // Tell the view which camera we want to use view.camera = camera @@ -162,6 +159,14 @@ class MainActivity : Activity() { private fun loadMaterial() { readUncompressedAsset("materials/baked_color.filamat").let { material = Material.Builder().payload(it, it.remaining()).build(engine) + material.compile( + Material.CompilerPriorityQueue.HIGH, + Material.UserVariantFilterBit.ALL, + Handler(Looper.getMainLooper())) { + android.util.Log.i("hellotriangle", + "Material " + material.name + " compiled.") + } + engine.flush() } } diff --git a/build.sh b/build.sh index 7c9bba84a20..56562cc1c6c 100755 --- a/build.sh +++ b/build.sh @@ -191,6 +191,7 @@ function build_clean { rm -Rf android/filamat-android/build android/filamat-android/.externalNativeBuild android/filamat-android/.cxx rm -Rf android/gltfio-android/build android/gltfio-android/.externalNativeBuild android/gltfio-android/.cxx rm -Rf android/filament-utils-android/build android/filament-utils-android/.externalNativeBuild android/filament-utils-android/.cxx + rm -f compile_commands.json } function build_clean_aggressive { @@ -232,6 +233,8 @@ function build_desktop_target { ${ASAN_UBSAN_OPTION} \ ${architectures} \ ../.. + ln -sf "out/cmake-${lc_target}/compile_commands.json" \ + ../../compile_commands.json fi ${BUILD_COMMAND} ${build_targets} @@ -287,6 +290,8 @@ function build_webgl_with_target { -DCMAKE_INSTALL_PREFIX="../webgl-${lc_target}/filament" \ -DWEBGL=1 \ ../.. + ln -sf "out/cmake-webgl-${lc_target}/compile_commands.json" \ + ../../compile_commands.json ${BUILD_COMMAND} ${BUILD_TARGETS} ) fi @@ -359,6 +364,8 @@ function build_android_target { ${MATOPT_OPTION} \ ${VULKAN_ANDROID_OPTION} \ ../.. + ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \ + ../../compile_commands.json fi # We must always install Android libraries to build the AAR @@ -509,7 +516,7 @@ function build_android { if [[ "${BUILD_ANDROID_SAMPLES}" == "true" ]]; then for sample in ${ANDROID_SAMPLES}; do echo "Installing out/${sample}-debug.apk" - cp samples/${sample}/build/outputs/apk/debug/${sample}-debug-unsigned.apk \ + cp samples/${sample}/build/outputs/apk/debug/${sample}-debug.apk \ ../out/${sample}-debug.apk done fi @@ -591,6 +598,8 @@ function build_ios_target { ${MATDBG_OPTION} \ ${MATOPT_OPTION} \ ../.. + ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \ + ../../compile_commands.json fi ${BUILD_COMMAND} diff --git a/build/android/ndk.version b/build/android/ndk.version index 46e2cdd8608..365a3c41561 100644 --- a/build/android/ndk.version +++ b/build/android/ndk.version @@ -1 +1 @@ -25.1.8937393 \ No newline at end of file +26.1.10909125 \ No newline at end of file diff --git a/build/common/bump-version.sh b/build/common/bump-version.sh index 7d201880fbe..f8c11d935d9 100755 --- a/build/common/bump-version.sh +++ b/build/common/bump-version.sh @@ -50,7 +50,7 @@ function replace { FIND_STR="${1//\{\{VERSION\}\}/${VERSION_REGEX}}" REPLACE_STR="${1//\{\{VERSION\}\}/${NEW_VERSION}}" local FILE_NAME="$2" - if [ IS_DARWIN ]; then + if [ $IS_DARWIN == 1 ]; then sed -i '' -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}" else sed -i -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}" diff --git a/build/linux/combine-static-libs.sh b/build/linux/combine-static-libs.sh index 4d0c7930812..71157dafa33 100755 --- a/build/linux/combine-static-libs.sh +++ b/build/linux/combine-static-libs.sh @@ -125,7 +125,13 @@ if [[ "${has_universal}" == "true" ]]; then arch_output="${OUTPUT_PATH%.a}_${arch}.a" arch_outputs+=("$arch_output") - combine_static_libs "$arch_output" $(find "$(pwd)/${archs_temp_dir}/${arch}" -iname '*.a') + + archives=() + while IFS= read -r -d $'\0'; do + archives+=("$REPLY") + done < <(find "$(pwd)/${archs_temp_dir}/${arch}" -iname '*.a' -print0) + + combine_static_libs "$arch_output" "$archives" done # Finally, combine the single-architecture archives into a universal binary. diff --git a/build/toolchain-arm7-linux-android.cmake b/build/toolchain-arm7-linux-android.cmake index ce94ab5659c..a3f6036c320 100644 --- a/build/toolchain-arm7-linux-android.cmake +++ b/build/toolchain-arm7-linux-android.cmake @@ -21,7 +21,7 @@ set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_VERSION 1) # android -set(API_LEVEL 19) +set(API_LEVEL 21) # architecture set(ARCH armv7a-linux-androideabi) diff --git a/build/toolchain-x86-linux-android.cmake b/build/toolchain-x86-linux-android.cmake index 88e1b1a79a7..8c50712337f 100644 --- a/build/toolchain-x86-linux-android.cmake +++ b/build/toolchain-x86-linux-android.cmake @@ -21,7 +21,7 @@ set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_VERSION 1) # android -set(API_LEVEL 19) +set(API_LEVEL 21) # architecture set(ARCH i686-linux-android) diff --git a/docs/Materials.md.html b/docs/Materials.md.html index 1e63bcef0da..d34b5f5ef4e 100644 --- a/docs/Materials.md.html +++ b/docs/Materials.md.html @@ -1397,7 +1397,7 @@ : `string` Value -: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`. Defaults to `opaque`. +: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`, `custom`. Defaults to `opaque`. Description : Defines how/if the rendered object is blended with the content of the render target. @@ -1420,6 +1420,7 @@ of the material's output defines whether a fragment is discarded or not. Additionally, ALPHA_TO_COVERAGE is enabled for non-translucent views. See the maskThreshold section for more information. + - **Custom**: blending is enabled. But the blending function is user specified. See `blendFunction`. !!! Note When `blending` is set to `masked`, alpha to coverage is automatically enabled for the material. @@ -1432,6 +1433,36 @@ } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Blending and transparency: blendFunction + +Type +: `object` + +Fields +: `srcRGB`, `srcA`, `dstRGB`, `dstA` + +Description +: - *srcRGB*: source function applied to the RGB channels + - *srcA*: source function applied to the alpha channel + - *srcRGB*: destination function applied to the RGB channels + - *srcRGB*: destination function applied to the alpha channel + The values possible for each functions are one of `zero`, `one`, `srcColor`, `oneMinusSrcColor`, + `dstColor`, `oneMinusDstColor`, `srcAlpha`, `oneMinusSrcAlpha`, `dstAlpha`, + `oneMinusDstAlpha`, `srcAlphaSaturate` + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON +material { + blending : custom, + blendFunction : + { + srcRGB: one, + srcA: one, + dstRGB: oneMinusSrcColor, + dstA: oneMinusSrcAlpha + } + } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ### Blending and transparency: postLightingBlending Type diff --git a/docs/remote/filament.js b/docs/remote/filament.js index 8fbaf161c5c..aff3ce6e598 100644 --- a/docs/remote/filament.js +++ b/docs/remote/filament.js @@ -6,7 +6,7 @@ var Filament = (() => { function(Filament) { Filament = Filament || {}; -var Module=typeof Filament!="undefined"?Filament:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}var fs;var nodePath;var requireNodeFS;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}requireNodeFS=()=>{if(!nodePath){fs=require("fs");nodePath=require("path")}};read_=function shell_read(filename,binary){requireNodeFS();filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{requireNodeFS();filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var tempRet0=0;var setTempRet0=value=>{tempRet0=value};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="filament.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["rc"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["tc"];addOnInit(Module["asm"]["sc"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;var ASM_CONSTS={1306044:()=>{const options=window.filament_glOptions;const context=window.filament_glContext;const handle=GL.registerContext(context,options);window.filament_contextHandle=handle;GL.makeContextCurrent(handle)},1306258:()=>{const handle=window.filament_contextHandle;GL.makeContextCurrent(handle)},1306339:($0,$1,$2,$3,$4,$5)=>{const fn=Emval.toValue($0);fn({"renderable":Emval.toValue($1),"depth":$2,"fragCoords":[$3,$4,$5]})}};function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{path=PATH_FS.resolve(FS.cwd(),path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(p=>!!p),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream||!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(ret.exists){return ret.object}else{return null}},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=FS.getStream(dirfd);if(!dirstream)throw new FS.ErrnoError(8);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}return name}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}}function __embind_finalize_value_array(rawTupleType){var reg=tupleRegistrations[rawTupleType];delete tupleRegistrations[rawTupleType];var elements=reg.elements;var elementsLength=elements.length;var elementTypes=elements.map(function(elt){return elt.getterReturnType}).concat(elements.map(function(elt){return elt.setterArgumentType}));var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;whenDependentTypesAreResolved([rawTupleType],elementTypes,function(elementTypes){elements.forEach((elt,i)=>{var getterReturnType=elementTypes[i];var getter=elt.getter;var getterContext=elt.getterContext;var setterArgumentType=elementTypes[i+elementsLength];var setter=elt.setter;var setterContext=elt.setterContext;elt.read=ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))};elt.write=(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}});return[{name:reg.name,"fromWireType":function(ptr){var rv=new Array(elementsLength);for(var i=0;ifield.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))},write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":function(ptr){var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":function(destructors,o){for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError('Missing field: "'+fieldName+'"')}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}var name=registeredInstance.name;if(!rawType){throwBindingError('type "'+name+'" must have a positive integer typeid pointer')}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError("Cannot register type '"+name+"' twice")}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function __embind_register_bool(rawType,name,size,trueValue,falseValue){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":8,"readValueFromPointer":function(pointer){var heap;if(size===1){heap=HEAP8}else if(size===2){heap=HEAP16}else if(size===4){heap=HEAP32}else{throw new TypeError("Unknown boolean type size: "+name)}return this["fromWireType"](heap[pointer>>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationRegistry=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}var registeredPointers={};function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}var delayFunction=undefined;function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function attachFinalizer(handle){if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError("unknown function pointer with signature "+signature+": "+rawFunction)}return fp}var UnboundTypeError=undefined;function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_class(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor){name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);if(upcast){upcast=embind__requireFunction(upcastSignature,upcast)}if(downcast){downcast=embind__requireFunction(downcastSignature,downcast)}rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError("Cannot construct "+name+" due to unbound types",[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],function(base){base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(legalFunctionName,function(){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[arguments.length];if(undefined===body){throw new BindingError("Tried to invoke ctor of "+name+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(registeredClass.constructor_body).toString()+") parameters instead!")}return body.apply(this,arguments)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})}function new_(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError("new_ called with constructor type "+typeof constructor+" which is not a function")}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})}function __embind_register_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})}function validateThis(this_,classType,humanName){if(!(this_ instanceof Object)){throwBindingError(humanName+' with invalid "this": '+this_)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(humanName+' incompatible with "this" of type '+this_.constructor.name)}if(!this_.$$.ptr){throwBindingError("cannot call emscripten binding method "+humanName+" on deleted object")}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)}function __embind_register_class_property(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){fieldName=readLatin1String(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],function(classType){classType=classType[0];var humanName=classType.name+"."+fieldName;var desc={get:function(){throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>{throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])}}else{desc.set=v=>{throwBindingError(humanName+" is a read-only property")}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],function(types){var getterReturnType=types[0];var desc={get:function(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType["fromWireType"](getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})}var emval_free_list=[];var emval_handle_array=[{},{value:undefined},{value:null},{value:true},{value:false}];function __emval_decref(handle){if(handle>4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{var handle=emval_free_list.length?emval_free_list.pop():emval_handle_array.length;emval_handle_array[handle]={refcount:1,value:value};return handle}}}};function __embind_register_emval(rawType,name){name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(handle){var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":function(destructors,value){return Emval.toHandle(value)},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})}function enumReadValueFromPointer(name,shift,signed){switch(shift){case 0:return function(pointer){var heap=signed?HEAP8:HEAPU8;return this["fromWireType"](heap[pointer])};case 1:return function(pointer){var heap=signed?HEAP16:HEAPU16;return this["fromWireType"](heap[pointer>>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function embindRepr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function __embind_register_function(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn){var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError("Cannot call "+name+" due to unbound types",argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn),argCount-1);return[]})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;iHEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value=="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function __emscripten_date_now(){return Date.now()}function __emscripten_err(str){err(UTF8ToString(str))}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function __emscripten_out(str){out(UTF8ToString(str))}function __emval_as(handle,returnType,destructorsRef){handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=Emval.toHandle(destructors);HEAPU32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_get_property(handle,key){handle=Emval.toValue(handle);key=Emval.toValue(key);return Emval.toHandle(handle[key])}function __emval_incref(handle){if(handle>4){emval_handle_array[handle].refcount+=1}}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol}function __emval_new_cstring(v){return Emval.toHandle(getStringOrSymbol(v))}function __emval_run_destructors(handle){var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)}function __emval_take_value(type,arg){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)}function _abort(){abort("")}var readAsmConstArgsArray=[];function readAsmConstArgs(sigPtr,buf){readAsmConstArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readAsmConstArgsArray.push(ch==105?HEAP32[buf]:HEAPF64[buf++>>1]);++buf}return readAsmConstArgsArray}function _emscripten_asm_const_int(code,sigPtr,argbuf){var args=readAsmConstArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}function getHeapMax(){return 2147483648}function _emscripten_get_heap_max(){return getHeapMax()}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=()=>{var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else _emscripten_get_now=()=>performance.now();function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _getentropy(buffer,size){if(!_getentropy.randomDevice){_getentropy.randomDevice=getRandomDevice()}for(var i=0;i>0]=_getentropy.randomDevice()}return 0}function __webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw(ctx){return!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"))}var GL={counter:1,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],byteSizeByTypeRoot:5120,byteSizeByType:[1,1,2,2,4,4,4,2,3,4,8],stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i>1;var quadIndexes=new Uint16Array(numIndexes);var i=0,v=0;while(1){quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+1;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v+3;if(i>=numIndexes)break;v+=4}context.GLctx.bufferData(34963,quadIndexes,35044);context.GLctx.bindBuffer(34963,null)}},getTempVertexBuffer:function getTempVertexBuffer(sizeBytes){var idx=GL.log2ceilLookup(sizeBytes);var ringbuffer=GL.currentContext.tempVertexBuffers1[idx];var nextFreeBufferIndex=GL.currentContext.tempVertexBufferCounters1[idx];GL.currentContext.tempVertexBufferCounters1[idx]=GL.currentContext.tempVertexBufferCounters1[idx]+1&GL.numTempVertexBuffersPerSize-1;var vbo=ringbuffer[nextFreeBufferIndex];if(vbo){return vbo}var prevVBO=GLctx.getParameter(34964);ringbuffer[nextFreeBufferIndex]=GLctx.createBuffer();GLctx.bindBuffer(34962,ringbuffer[nextFreeBufferIndex]);GLctx.bufferData(34962,1<>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},calcBufLength:function calcBufLength(size,type,stride,count){if(stride>0){return count*stride}var typeSize=GL.byteSizeByType[type-GL.byteSizeByTypeRoot];return size*typeSize*count},usedTempBuffers:[],preDrawHandleClientVertexAttribBindings:function preDrawHandleClientVertexAttribBindings(count){GL.resetBufferBinding=false;for(var i=0;i=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}__webgl_enable_WEBGL_multi_draw(GLctx);var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}})}};function _glActiveTexture(x0){GLctx["activeTexture"](x0)}function _glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}function _glBeginQuery(target,id){GLctx["beginQuery"](target,GL.queries[id])}function _glBindAttribLocation(program,index,name){GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))}function _glBindBuffer(target,buffer){if(target==34962){GLctx.currentArrayBufferBinding=buffer}else if(target==34963){GLctx.currentElementArrayBufferBinding=buffer}if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])}function _glBindBufferBase(target,index,buffer){GLctx["bindBufferBase"](target,index,GL.buffers[buffer])}function _glBindBufferRange(target,index,buffer,offset,ptrsize){GLctx["bindBufferRange"](target,index,GL.buffers[buffer],offset,ptrsize)}function _glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])}function _glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}function _glBindSampler(unit,sampler){GLctx["bindSampler"](unit,GL.samplers[sampler])}function _glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}function _glBindVertexArray(vao){GLctx["bindVertexArray"](GL.vaos[vao]);var ibo=GLctx.getParameter(34965);GLctx.currentElementArrayBufferBinding=ibo?ibo.name|0:0}function _glBlendEquationSeparate(x0,x1){GLctx["blendEquationSeparate"](x0,x1)}function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}function _glBlitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9){GLctx["blitFramebuffer"](x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)}function _glBufferData(target,size,data,usage){if(true){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}}else{GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}}function _glBufferSubData(target,offset,size,data){if(true){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}function _glClear(x0){GLctx["clear"](x0)}function _glClearBufferfi(x0,x1,x2,x3){GLctx["clearBufferfi"](x0,x1,x2,x3)}function _glClearBufferfv(buffer,drawbuffer,value){GLctx["clearBufferfv"](buffer,drawbuffer,HEAPF32,value>>2)}function _glClearBufferiv(buffer,drawbuffer,value){GLctx["clearBufferiv"](buffer,drawbuffer,HEAP32,value>>2)}function _glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}function _glClearDepthf(x0){GLctx["clearDepth"](x0)}function _glClearStencil(x0){GLctx["clearStencil"](x0)}function convertI32PairToI53(lo,hi){return(lo>>>0)+hi*4294967296}function _glClientWaitSync(sync,flags,timeoutLo,timeoutHi){return GLctx.clientWaitSync(GL.syncs[sync],flags,convertI32PairToI53(timeoutLo,timeoutHi))}function _glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}function _glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}function _glCompressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data){if(true){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,imageSize,data)}else{GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,HEAPU8,data,imageSize)}return}GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,data?HEAPU8.subarray(data,data+imageSize):null)}function _glCompressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data){if(GLctx.currentPixelUnpackBufferBinding){GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}}function _glCopyBufferSubData(x0,x1,x2,x3,x4){GLctx["copyBufferSubData"](x0,x1,x2,x3,x4)}function _glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id}function _glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}function _glCullFace(x0){GLctx["cullFace"](x0)}function _glDeleteBuffers(n,buffers){for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentArrayBufferBinding)GLctx.currentArrayBufferBinding=0;if(id==GLctx.currentElementArrayBufferBinding)GLctx.currentElementArrayBufferBinding=0;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}}function _glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}function _glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null}function _glDeleteQueries(n,ids){for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx["deleteQuery"](query);GL.queries[id]=null}}function _glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}function _glDeleteSamplers(n,samplers){for(var i=0;i>2];var sampler=GL.samplers[id];if(!sampler)continue;GLctx["deleteSampler"](sampler);sampler.name=0;GL.samplers[id]=null}}function _glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}function _glDeleteSync(id){if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null}function _glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}function _glDeleteVertexArrays(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}function _glDepthFunc(x0){GLctx["depthFunc"](x0)}function _glDepthMask(flag){GLctx.depthMask(!!flag)}function _glDepthRangef(x0,x1){GLctx["depthRange"](x0,x1)}function _glDetachShader(program,shader){GLctx.detachShader(GL.programs[program],GL.shaders[shader])}function _glDisable(x0){GLctx["disable"](x0)}function _glDisableVertexAttribArray(index){var cb=GL.currentContext.clientBuffers[index];cb.enabled=false;GLctx.disableVertexAttribArray(index)}var tempFixedLengthArray=[];function _glDrawBuffers(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}function _glDrawElements(mode,count,type,indices){var buf;if(!GLctx.currentElementArrayBufferBinding){var size=GL.calcBufLength(1,type,0,count);buf=GL.getTempIndexBuffer(size);GLctx.bindBuffer(34963,buf);GLctx.bufferSubData(34963,0,HEAPU8.subarray(indices,indices+size));indices=0}GL.preDrawHandleClientVertexAttribBindings(count);GLctx.drawElements(mode,count,type,indices);GL.postDrawHandleClientVertexAttribBindings(count);if(!GLctx.currentElementArrayBufferBinding){GLctx.bindBuffer(34963,null)}}function _glDrawElementsInstanced(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}function _glEnable(x0){GLctx["enable"](x0)}function _glEnableVertexAttribArray(index){var cb=GL.currentContext.clientBuffers[index];cb.enabled=true;GLctx.enableVertexAttribArray(index)}function _glEndQuery(x0){GLctx["endQuery"](x0)}function _glFenceSync(condition,flags){var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}else{return 0}}function _glFinish(){GLctx["finish"]()}function _glFlush(){GLctx["flush"]()}function _glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}function _glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}function _glFramebufferTextureLayer(target,attachment,texture,level,layer){GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)}function _glFrontFace(x0){GLctx["frontFace"](x0)}function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i>2]=id}}function _glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}function _glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}function _glGenQueries(n,ids){__glGenObject(n,ids,"createQuery",GL.queries)}function _glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}function _glGenSamplers(n,samplers){__glGenObject(n,samplers,"createSampler",GL.samplers)}function _glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}function _glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}function _glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}function _glGetBufferSubData(target,offset,size,data){if(!data){GL.recordError(1281);return}size&&GLctx["getBufferSubData"](target,offset,HEAPU8,data,size)}function _glGetError(){var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error}function writeI53ToI64(ptr,num){HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}var exts=GLctx.getSupportedExtensions()||[];ret=2*exts.length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}function _glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}function _glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}function _glGetProgramBinary(program,bufSize,length,binaryFormat,binary){GL.recordError(1282)}function _glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}}function _glGetQueryObjectuiv(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx["getQueryParameter"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}function _glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}function stringToNewUTF8(jsString){var length=lengthBytesUTF8(jsString)+1;var cString=_malloc(length);stringToUTF8(jsString,cString,length);return cString}function _glGetString(name_){var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);if(true)glVersion="OpenGL ES 3.0 ("+glVersion+")";else{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret}function _glGetUniformBlockIndex(program,uniformBlockName){return GLctx["getUniformBlockIndex"](GL.programs[program],UTF8ToString(uniformBlockName))}function jstoi_q(str){return parseInt(str)}function webglGetLeftBracePos(name){return name.slice(-1)=="]"&&name.lastIndexOf("[")}function webglPrepareUniformLocationsBeforeFirstUse(program){var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex>2]}GLctx["invalidateFramebuffer"](target,list)}function _glLinkProgram(program){program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}}function emscriptenWebGLGetBufferBinding(target){switch(target){case 34962:target=34964;break;case 34963:target=34965;break;case 35051:target=35053;break;case 35052:target=35055;break;case 35982:target=35983;break;case 36662:target=36662;break;case 36663:target=36663;break;case 35345:target=35368;break}var buffer=GLctx.getParameter(target);if(buffer)return buffer.name|0;else return 0}function emscriptenWebGLValidateMapBufferTarget(target){switch(target){case 34962:case 34963:case 36662:case 36663:case 35051:case 35052:case 35882:case 35982:case 35345:return true;default:return false}}function _glMapBufferRange(target,offset,length,access){if(access!=26&&access!=10){err("glMapBufferRange is only supported when access is MAP_WRITE|INVALIDATE_BUFFER");return 0}if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glMapBufferRange");return 0}var mem=_malloc(length);if(!mem)return 0;GL.mappedBuffers[emscriptenWebGLGetBufferBinding(target)]={offset:offset,length:length,mem:mem,access:access};return mem}function _glPixelStorei(pname,param){if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)}function _glPolygonOffset(x0,x1){GLctx["polygonOffset"](x0,x1)}function _glProgramBinary(program,binaryFormat,binary,length){GL.recordError(1280)}function computeUnpackAlignedImageSize(width,height,sizePerPixel,alignment){function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=width*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,alignment);return height*alignedRowSize}function __colorChannelsInGlTextureFormat(format){var colorChannels={5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4};return colorChannels[format-6402]||1}function heapObjectForWebGLType(type){type-=5120;if(type==0)return HEAP8;if(type==1)return HEAPU8;if(type==2)return HEAP16;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922||type==28520||type==30779||type==30782)return HEAPU32;return HEAPU16}function heapAccessShiftForWebGLHeap(heap){return 31-Math.clz32(heap.BYTES_PER_ELEMENT)}function emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat){var heap=heapObjectForWebGLType(type);var shift=heapAccessShiftForWebGLHeap(heap);var byteSize=1<>shift,pixels+bytes>>shift)}function _glReadPixels(x,y,width,height,format,type,pixels){if(true){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels)}else{var heap=heapObjectForWebGLType(type);GLctx.readPixels(x,y,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}function _glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}function _glRenderbufferStorageMultisample(x0,x1,x2,x3,x4){GLctx["renderbufferStorageMultisample"](x0,x1,x2,x3,x4)}function _glSamplerParameterf(sampler,pname,param){GLctx["samplerParameterf"](GL.samplers[sampler],pname,param)}function _glSamplerParameteri(sampler,pname,param){GLctx["samplerParameteri"](GL.samplers[sampler],pname,param)}function _glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}function _glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}function _glStencilFuncSeparate(x0,x1,x2,x3){GLctx["stencilFuncSeparate"](x0,x1,x2,x3)}function _glStencilMaskSeparate(x0,x1){GLctx["stencilMaskSeparate"](x0,x1)}function _glStencilOpSeparate(x0,x1,x2,x3){GLctx["stencilOpSeparate"](x0,x1,x2,x3)}function _glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,null)}return}GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}function _glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}function _glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}function _glTexStorage2D(x0,x1,x2,x3,x4){GLctx["texStorage2D"](x0,x1,x2,x3,x4)}function _glTexStorage3D(x0,x1,x2,x3,x4,x5){GLctx["texStorage3D"](x0,x1,x2,x3,x4,x5)}function _glTexSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels){if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,null)}return}var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)}function _glTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels){if(GLctx.currentPixelUnpackBufferBinding){GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}}function webglGetUniformLocation(location){var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?"["+webglLoc+"]":""))}return webglLoc}else{GL.recordError(1282)}}function _glUniform1fv(location,count,value){count&&GLctx.uniform1fv(webglGetUniformLocation(location),HEAPF32,value>>2,count)}function _glUniform1i(location,v0){GLctx.uniform1i(webglGetUniformLocation(location),v0)}function _glUniform1iv(location,count,value){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count)}function _glUniform2fv(location,count,value){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2)}function _glUniform2iv(location,count,value){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2)}function _glUniform3fv(location,count,value){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3)}function _glUniform3iv(location,count,value){count&&GLctx.uniform3iv(webglGetUniformLocation(location),HEAP32,value>>2,count*3)}function _glUniform4fv(location,count,value){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4)}function _glUniform4iv(location,count,value){count&&GLctx.uniform4iv(webglGetUniformLocation(location),HEAP32,value>>2,count*4)}function _glUniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding){program=GL.programs[program];GLctx["uniformBlockBinding"](program,uniformBlockIndex,uniformBlockBinding)}function _glUniformMatrix3fv(location,count,transpose,value){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9)}function _glUniformMatrix4fv(location,count,transpose,value){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16)}function _glUnmapBuffer(target){if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glUnmapBuffer");return 0}var buffer=emscriptenWebGLGetBufferBinding(target);var mapping=GL.mappedBuffers[buffer];if(!mapping){GL.recordError(1282);err("buffer was never mapped in glUnmapBuffer");return 0}GL.mappedBuffers[buffer]=null;if(!(mapping.access&16))if(true){GLctx.bufferSubData(target,mapping.offset,HEAPU8,mapping.mem,mapping.length)}else{GLctx.bufferSubData(target,mapping.offset,HEAPU8.subarray(mapping.mem,mapping.mem+mapping.length))}_free(mapping.mem);return 1}function _glUseProgram(program){program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program}function _glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}function _glVertexAttribI4ui(x0,x1,x2,x3,x4){GLctx["vertexAttribI4ui"](x0,x1,x2,x3,x4)}function _glVertexAttribIPointer(index,size,type,stride,ptr){var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=false;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribIPointer(index,size,type,stride,ptr)};return}cb.clientside=false;GLctx["vertexAttribIPointer"](index,size,type,stride,ptr)}function _glVertexAttribPointer(index,size,type,normalized,stride,ptr){var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=normalized;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribPointer(index,size,type,normalized,stride,ptr)};return}cb.clientside=false;GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}function _glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}function _setTempRet0(val){setTempRet0(val)}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}else{return thisDate.getFullYear()}}else{return thisDate.getFullYear()-1}}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}else{return"PM"}},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm){return _strftime(s,maxsize,format,tm)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var asmLibraryArg={"Ea":___syscall_fcntl64,"Jb":___syscall_ioctl,"Kb":___syscall_openat,"Fb":___syscall_stat64,"y":__embind_finalize_value_array,"l":__embind_finalize_value_object,"zb":__embind_register_bigint,"Qb":__embind_register_bool,"e":__embind_register_class,"k":__embind_register_class_class_function,"n":__embind_register_class_constructor,"a":__embind_register_class_function,"x":__embind_register_class_property,"Pb":__embind_register_emval,"h":__embind_register_enum,"b":__embind_register_enum_value,"Ga":__embind_register_float,"X":__embind_register_function,"C":__embind_register_integer,"q":__embind_register_memory_view,"Fa":__embind_register_std_string,"ia":__embind_register_std_wstring,"z":__embind_register_value_array,"f":__embind_register_value_array_element,"m":__embind_register_value_object,"d":__embind_register_value_object_field,"Rb":__embind_register_void,"Nb":__emscripten_date_now,"xb":__emscripten_err,"Mb":__emscripten_get_now_is_monotonic,"Ba":__emscripten_out,"s":__emval_as,"g":__emval_decref,"t":__emval_get_property,"W":__emval_incref,"G":__emval_new_cstring,"r":__emval_run_destructors,"u":__emval_take_value,"c":_abort,"ja":_emscripten_asm_const_int,"Eb":_emscripten_get_heap_max,"Lb":_emscripten_get_now,"Ob":_emscripten_memcpy_big,"Db":_emscripten_resize_heap,"Gb":_environ_get,"Hb":_environ_sizes_get,"ha":_fd_close,"Ib":_fd_read,"yb":_fd_seek,"Da":_fd_write,"Bb":_getentropy,"i":_glActiveTexture,"ga":_glAttachShader,"jb":_glBeginQuery,"rb":_glBindAttribLocation,"p":_glBindBuffer,"qa":_glBindBufferBase,"ka":_glBindBufferRange,"o":_glBindFramebuffer,"La":_glBindRenderbuffer,"ea":_glBindSampler,"j":_glBindTexture,"fb":_glBindVertexArray,"ta":_glBlendEquationSeparate,"sa":_glBlendFuncSeparate,"Qa":_glBlitFramebuffer,"E":_glBufferData,"Y":_glBufferSubData,"Vb":_glClear,"_b":_glClearBufferfi,"D":_glClearBufferfv,"Zb":_glClearBufferiv,"Yb":_glClearColor,"Xb":_glClearDepthf,"Wb":_glClearStencil,"Ab":_glClientWaitSync,"ba":_glColorMask,"sb":_glCompileShader,"Ia":_glCompressedTexSubImage2D,"Ha":_glCompressedTexSubImage3D,"kc":_glCopyBufferSubData,"Aa":_glCreateProgram,"ub":_glCreateShader,"ua":_glCullFace,"da":_glDeleteBuffers,"la":_glDeleteFramebuffers,"U":_glDeleteProgram,"ib":_glDeleteQueries,"Sa":_glDeleteRenderbuffers,"wa":_glDeleteSamplers,"M":_glDeleteShader,"Pa":_glDeleteSync,"Ta":_glDeleteTextures,"gb":_glDeleteVertexArrays,"fa":_glDepthFunc,"aa":_glDepthMask,"na":_glDepthRangef,"N":_glDetachShader,"v":_glDisable,"bc":_glDisableVertexAttribArray,"nc":_glDrawBuffers,"jc":_glDrawElements,"ic":_glDrawElementsInstanced,"A":_glEnable,"ec":_glEnableVertexAttribArray,"kb":_glEndQuery,"Z":_glFenceSync,"za":_glFinish,"nb":_glFlush,"P":_glFramebufferRenderbuffer,"Na":_glFramebufferTexture2D,"Ma":_glFramebufferTextureLayer,"va":_glFrontFace,"R":_glGenBuffers,"Ua":_glGenFramebuffers,"hb":_glGenQueries,"$":_glGenRenderbuffers,"ya":_glGenSamplers,"Q":_glGenTextures,"mb":_glGenVertexArrays,"lc":_glGenerateMipmap,"Ub":_glGetBufferSubData,"V":_glGetError,"db":_glGetFloatv,"B":_glGetIntegerv,"vb":_glGetProgramBinary,"ob":_glGetProgramInfoLog,"O":_glGetProgramiv,"lb":_glGetQueryObjectuiv,"pb":_glGetShaderInfoLog,"L":_glGetShaderiv,"H":_glGetString,"bb":_glGetUniformBlockIndex,"_":_glGetUniformLocation,"cb":_glHint,"eb":_glInvalidateFramebuffer,"qb":_glLinkProgram,"Tb":_glMapBufferRange,"K":_glPixelStorei,"ra":_glPolygonOffset,"wb":_glProgramBinary,"Ra":_glReadPixels,"$b":_glRenderbufferStorage,"ac":_glRenderbufferStorageMultisample,"xa":_glSamplerParameterf,"I":_glSamplerParameteri,"pa":_glScissor,"tb":_glShaderSource,"T":_glStencilFuncSeparate,"F":_glStencilMaskSeparate,"S":_glStencilOpSeparate,"J":_glTexImage2D,"mc":_glTexParameterf,"w":_glTexParameteri,"hc":_glTexStorage2D,"Oa":_glTexStorage3D,"Ka":_glTexSubImage2D,"Ja":_glTexSubImage3D,"$a":_glUniform1fv,"ma":_glUniform1i,"Xa":_glUniform1iv,"_a":_glUniform2fv,"Wa":_glUniform2iv,"Za":_glUniform3fv,"Va":_glUniform3iv,"Ya":_glUniform4fv,"qc":_glUniform4iv,"ab":_glUniformBlockBinding,"pc":_glUniformMatrix3fv,"oc":_glUniformMatrix4fv,"Sb":_glUnmapBuffer,"ca":_glUseProgram,"cc":_glVertexAttrib4f,"dc":_glVertexAttribI4ui,"gc":_glVertexAttribIPointer,"fc":_glVertexAttribPointer,"oa":_glViewport,"Ca":_setTempRet0,"Cb":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["sc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["uc"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["vc"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["wc"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["xc"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["yc"]).apply(null,arguments)};var dynCall_iiiiij=Module["dynCall_iiiiij"]=function(){return(dynCall_iiiiij=Module["dynCall_iiiiij"]=Module["asm"]["zc"]).apply(null,arguments)};var dynCall_jii=Module["dynCall_jii"]=function(){return(dynCall_jii=Module["dynCall_jii"]=Module["asm"]["Ac"]).apply(null,arguments)};var dynCall_iiij=Module["dynCall_iiij"]=function(){return(dynCall_iiij=Module["dynCall_iiij"]=Module["asm"]["Bc"]).apply(null,arguments)};var dynCall_iiiij=Module["dynCall_iiiij"]=function(){return(dynCall_iiiij=Module["dynCall_iiiij"]=Module["asm"]["Cc"]).apply(null,arguments)};var dynCall_vij=Module["dynCall_vij"]=function(){return(dynCall_vij=Module["dynCall_vij"]=Module["asm"]["Dc"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["Ec"]).apply(null,arguments)};var dynCall_viijii=Module["dynCall_viijii"]=function(){return(dynCall_viijii=Module["dynCall_viijii"]=Module["asm"]["Fc"]).apply(null,arguments)};var dynCall_iiiiijj=Module["dynCall_iiiiijj"]=function(){return(dynCall_iiiiijj=Module["dynCall_iiiiijj"]=Module["asm"]["Gc"]).apply(null,arguments)};var dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=function(){return(dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=Module["asm"]["Hc"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); +var Module=typeof Filament!="undefined"?Filament:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}var fs;var nodePath;var requireNodeFS;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}requireNodeFS=()=>{if(!nodePath){fs=require("fs");nodePath=require("path")}};read_=function shell_read(filename,binary){requireNodeFS();filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{requireNodeFS();filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var tempRet0=0;var setTempRet0=value=>{tempRet0=value};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="filament.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["rc"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["tc"];addOnInit(Module["asm"]["sc"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;var ASM_CONSTS={1436348:()=>{const options=window.filament_glOptions;const context=window.filament_glContext;const handle=GL.registerContext(context,options);window.filament_contextHandle=handle;GL.makeContextCurrent(handle)},1436562:()=>{const handle=window.filament_contextHandle;GL.makeContextCurrent(handle)},1436643:($0,$1,$2,$3,$4,$5)=>{const fn=Emval.toValue($0);fn({"renderable":Emval.toValue($1),"depth":$2,"fragCoords":[$3,$4,$5]})}};function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{path=PATH_FS.resolve(FS.cwd(),path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(p=>!!p),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream||!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(ret.exists){return ret.object}else{return null}},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=FS.getStream(dirfd);if(!dirstream)throw new FS.ErrnoError(8);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}return name}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}}function __embind_finalize_value_array(rawTupleType){var reg=tupleRegistrations[rawTupleType];delete tupleRegistrations[rawTupleType];var elements=reg.elements;var elementsLength=elements.length;var elementTypes=elements.map(function(elt){return elt.getterReturnType}).concat(elements.map(function(elt){return elt.setterArgumentType}));var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;whenDependentTypesAreResolved([rawTupleType],elementTypes,function(elementTypes){elements.forEach((elt,i)=>{var getterReturnType=elementTypes[i];var getter=elt.getter;var getterContext=elt.getterContext;var setterArgumentType=elementTypes[i+elementsLength];var setter=elt.setter;var setterContext=elt.setterContext;elt.read=ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))};elt.write=(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}});return[{name:reg.name,"fromWireType":function(ptr){var rv=new Array(elementsLength);for(var i=0;ifield.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))},write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":function(ptr){var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":function(destructors,o){for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError('Missing field: "'+fieldName+'"')}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}var name=registeredInstance.name;if(!rawType){throwBindingError('type "'+name+'" must have a positive integer typeid pointer')}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError("Cannot register type '"+name+"' twice")}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function __embind_register_bool(rawType,name,size,trueValue,falseValue){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":8,"readValueFromPointer":function(pointer){var heap;if(size===1){heap=HEAP8}else if(size===2){heap=HEAP16}else if(size===4){heap=HEAP32}else{throw new TypeError("Unknown boolean type size: "+name)}return this["fromWireType"](heap[pointer>>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationRegistry=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}var registeredPointers={};function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}var delayFunction=undefined;function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function attachFinalizer(handle){if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError("unknown function pointer with signature "+signature+": "+rawFunction)}return fp}var UnboundTypeError=undefined;function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_class(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor){name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);if(upcast){upcast=embind__requireFunction(upcastSignature,upcast)}if(downcast){downcast=embind__requireFunction(downcastSignature,downcast)}rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError("Cannot construct "+name+" due to unbound types",[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],function(base){base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(legalFunctionName,function(){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[arguments.length];if(undefined===body){throw new BindingError("Tried to invoke ctor of "+name+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(registeredClass.constructor_body).toString()+") parameters instead!")}return body.apply(this,arguments)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})}function new_(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError("new_ called with constructor type "+typeof constructor+" which is not a function")}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})}function __embind_register_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})}function validateThis(this_,classType,humanName){if(!(this_ instanceof Object)){throwBindingError(humanName+' with invalid "this": '+this_)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(humanName+' incompatible with "this" of type '+this_.constructor.name)}if(!this_.$$.ptr){throwBindingError("cannot call emscripten binding method "+humanName+" on deleted object")}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)}function __embind_register_class_property(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){fieldName=readLatin1String(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],function(classType){classType=classType[0];var humanName=classType.name+"."+fieldName;var desc={get:function(){throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>{throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])}}else{desc.set=v=>{throwBindingError(humanName+" is a read-only property")}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],function(types){var getterReturnType=types[0];var desc={get:function(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType["fromWireType"](getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})}var emval_free_list=[];var emval_handle_array=[{},{value:undefined},{value:null},{value:true},{value:false}];function __emval_decref(handle){if(handle>4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{var handle=emval_free_list.length?emval_free_list.pop():emval_handle_array.length;emval_handle_array[handle]={refcount:1,value:value};return handle}}}};function __embind_register_emval(rawType,name){name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(handle){var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":function(destructors,value){return Emval.toHandle(value)},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})}function enumReadValueFromPointer(name,shift,signed){switch(shift){case 0:return function(pointer){var heap=signed?HEAP8:HEAPU8;return this["fromWireType"](heap[pointer])};case 1:return function(pointer){var heap=signed?HEAP16:HEAPU16;return this["fromWireType"](heap[pointer>>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function embindRepr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function __embind_register_function(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn){var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError("Cannot call "+name+" due to unbound types",argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn),argCount-1);return[]})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;iHEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value=="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function __emscripten_date_now(){return Date.now()}function __emscripten_err(str){err(UTF8ToString(str))}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function __emscripten_out(str){out(UTF8ToString(str))}function __emval_as(handle,returnType,destructorsRef){handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=Emval.toHandle(destructors);HEAPU32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_get_property(handle,key){handle=Emval.toValue(handle);key=Emval.toValue(key);return Emval.toHandle(handle[key])}function __emval_incref(handle){if(handle>4){emval_handle_array[handle].refcount+=1}}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol}function __emval_new_cstring(v){return Emval.toHandle(getStringOrSymbol(v))}function __emval_run_destructors(handle){var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)}function __emval_take_value(type,arg){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)}function _abort(){abort("")}var readAsmConstArgsArray=[];function readAsmConstArgs(sigPtr,buf){readAsmConstArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readAsmConstArgsArray.push(ch==105?HEAP32[buf]:HEAPF64[buf++>>1]);++buf}return readAsmConstArgsArray}function _emscripten_asm_const_int(code,sigPtr,argbuf){var args=readAsmConstArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}function getHeapMax(){return 2147483648}function _emscripten_get_heap_max(){return getHeapMax()}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=()=>{var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else _emscripten_get_now=()=>performance.now();function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _getentropy(buffer,size){if(!_getentropy.randomDevice){_getentropy.randomDevice=getRandomDevice()}for(var i=0;i>0]=_getentropy.randomDevice()}return 0}function __webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw(ctx){return!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"))}var GL={counter:1,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],byteSizeByTypeRoot:5120,byteSizeByType:[1,1,2,2,4,4,4,2,3,4,8],stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i>1;var quadIndexes=new Uint16Array(numIndexes);var i=0,v=0;while(1){quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+1;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v+3;if(i>=numIndexes)break;v+=4}context.GLctx.bufferData(34963,quadIndexes,35044);context.GLctx.bindBuffer(34963,null)}},getTempVertexBuffer:function getTempVertexBuffer(sizeBytes){var idx=GL.log2ceilLookup(sizeBytes);var ringbuffer=GL.currentContext.tempVertexBuffers1[idx];var nextFreeBufferIndex=GL.currentContext.tempVertexBufferCounters1[idx];GL.currentContext.tempVertexBufferCounters1[idx]=GL.currentContext.tempVertexBufferCounters1[idx]+1&GL.numTempVertexBuffersPerSize-1;var vbo=ringbuffer[nextFreeBufferIndex];if(vbo){return vbo}var prevVBO=GLctx.getParameter(34964);ringbuffer[nextFreeBufferIndex]=GLctx.createBuffer();GLctx.bindBuffer(34962,ringbuffer[nextFreeBufferIndex]);GLctx.bufferData(34962,1<>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},calcBufLength:function calcBufLength(size,type,stride,count){if(stride>0){return count*stride}var typeSize=GL.byteSizeByType[type-GL.byteSizeByTypeRoot];return size*typeSize*count},usedTempBuffers:[],preDrawHandleClientVertexAttribBindings:function preDrawHandleClientVertexAttribBindings(count){GL.resetBufferBinding=false;for(var i=0;i=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}__webgl_enable_WEBGL_multi_draw(GLctx);var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}})}};function _glActiveTexture(x0){GLctx["activeTexture"](x0)}function _glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}function _glBeginQuery(target,id){GLctx["beginQuery"](target,GL.queries[id])}function _glBindAttribLocation(program,index,name){GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))}function _glBindBuffer(target,buffer){if(target==34962){GLctx.currentArrayBufferBinding=buffer}else if(target==34963){GLctx.currentElementArrayBufferBinding=buffer}if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])}function _glBindBufferBase(target,index,buffer){GLctx["bindBufferBase"](target,index,GL.buffers[buffer])}function _glBindBufferRange(target,index,buffer,offset,ptrsize){GLctx["bindBufferRange"](target,index,GL.buffers[buffer],offset,ptrsize)}function _glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])}function _glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}function _glBindSampler(unit,sampler){GLctx["bindSampler"](unit,GL.samplers[sampler])}function _glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}function _glBindVertexArray(vao){GLctx["bindVertexArray"](GL.vaos[vao]);var ibo=GLctx.getParameter(34965);GLctx.currentElementArrayBufferBinding=ibo?ibo.name|0:0}function _glBlendEquationSeparate(x0,x1){GLctx["blendEquationSeparate"](x0,x1)}function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}function _glBlitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9){GLctx["blitFramebuffer"](x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)}function _glBufferData(target,size,data,usage){if(true){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}}else{GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}}function _glBufferSubData(target,offset,size,data){if(true){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}function _glClear(x0){GLctx["clear"](x0)}function _glClearBufferfi(x0,x1,x2,x3){GLctx["clearBufferfi"](x0,x1,x2,x3)}function _glClearBufferfv(buffer,drawbuffer,value){GLctx["clearBufferfv"](buffer,drawbuffer,HEAPF32,value>>2)}function _glClearBufferiv(buffer,drawbuffer,value){GLctx["clearBufferiv"](buffer,drawbuffer,HEAP32,value>>2)}function _glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}function _glClearDepthf(x0){GLctx["clearDepth"](x0)}function _glClearStencil(x0){GLctx["clearStencil"](x0)}function convertI32PairToI53(lo,hi){return(lo>>>0)+hi*4294967296}function _glClientWaitSync(sync,flags,timeoutLo,timeoutHi){return GLctx.clientWaitSync(GL.syncs[sync],flags,convertI32PairToI53(timeoutLo,timeoutHi))}function _glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}function _glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}function _glCompressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data){if(true){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,imageSize,data)}else{GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,HEAPU8,data,imageSize)}return}GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,data?HEAPU8.subarray(data,data+imageSize):null)}function _glCompressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data){if(GLctx.currentPixelUnpackBufferBinding){GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}}function _glCopyBufferSubData(x0,x1,x2,x3,x4){GLctx["copyBufferSubData"](x0,x1,x2,x3,x4)}function _glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id}function _glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}function _glCullFace(x0){GLctx["cullFace"](x0)}function _glDeleteBuffers(n,buffers){for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentArrayBufferBinding)GLctx.currentArrayBufferBinding=0;if(id==GLctx.currentElementArrayBufferBinding)GLctx.currentElementArrayBufferBinding=0;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}}function _glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}function _glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null}function _glDeleteQueries(n,ids){for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx["deleteQuery"](query);GL.queries[id]=null}}function _glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}function _glDeleteSamplers(n,samplers){for(var i=0;i>2];var sampler=GL.samplers[id];if(!sampler)continue;GLctx["deleteSampler"](sampler);sampler.name=0;GL.samplers[id]=null}}function _glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}function _glDeleteSync(id){if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null}function _glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}function _glDeleteVertexArrays(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}function _glDepthFunc(x0){GLctx["depthFunc"](x0)}function _glDepthMask(flag){GLctx.depthMask(!!flag)}function _glDepthRangef(x0,x1){GLctx["depthRange"](x0,x1)}function _glDetachShader(program,shader){GLctx.detachShader(GL.programs[program],GL.shaders[shader])}function _glDisable(x0){GLctx["disable"](x0)}function _glDisableVertexAttribArray(index){var cb=GL.currentContext.clientBuffers[index];cb.enabled=false;GLctx.disableVertexAttribArray(index)}var tempFixedLengthArray=[];function _glDrawBuffers(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}function _glDrawElements(mode,count,type,indices){var buf;if(!GLctx.currentElementArrayBufferBinding){var size=GL.calcBufLength(1,type,0,count);buf=GL.getTempIndexBuffer(size);GLctx.bindBuffer(34963,buf);GLctx.bufferSubData(34963,0,HEAPU8.subarray(indices,indices+size));indices=0}GL.preDrawHandleClientVertexAttribBindings(count);GLctx.drawElements(mode,count,type,indices);GL.postDrawHandleClientVertexAttribBindings(count);if(!GLctx.currentElementArrayBufferBinding){GLctx.bindBuffer(34963,null)}}function _glDrawElementsInstanced(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}function _glEnable(x0){GLctx["enable"](x0)}function _glEnableVertexAttribArray(index){var cb=GL.currentContext.clientBuffers[index];cb.enabled=true;GLctx.enableVertexAttribArray(index)}function _glEndQuery(x0){GLctx["endQuery"](x0)}function _glFenceSync(condition,flags){var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}else{return 0}}function _glFinish(){GLctx["finish"]()}function _glFlush(){GLctx["flush"]()}function _glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}function _glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}function _glFramebufferTextureLayer(target,attachment,texture,level,layer){GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)}function _glFrontFace(x0){GLctx["frontFace"](x0)}function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i>2]=id}}function _glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}function _glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}function _glGenQueries(n,ids){__glGenObject(n,ids,"createQuery",GL.queries)}function _glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}function _glGenSamplers(n,samplers){__glGenObject(n,samplers,"createSampler",GL.samplers)}function _glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}function _glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}function _glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}function _glGetBufferSubData(target,offset,size,data){if(!data){GL.recordError(1281);return}size&&GLctx["getBufferSubData"](target,offset,HEAPU8,data,size)}function _glGetError(){var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error}function writeI53ToI64(ptr,num){HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}var exts=GLctx.getSupportedExtensions()||[];ret=2*exts.length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}function _glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}function _glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}function _glGetProgramBinary(program,bufSize,length,binaryFormat,binary){GL.recordError(1282)}function _glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}}function _glGetQueryObjectuiv(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx["getQueryParameter"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}function _glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}function stringToNewUTF8(jsString){var length=lengthBytesUTF8(jsString)+1;var cString=_malloc(length);stringToUTF8(jsString,cString,length);return cString}function _glGetString(name_){var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);if(true)glVersion="OpenGL ES 3.0 ("+glVersion+")";else{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret}function _glGetUniformBlockIndex(program,uniformBlockName){return GLctx["getUniformBlockIndex"](GL.programs[program],UTF8ToString(uniformBlockName))}function jstoi_q(str){return parseInt(str)}function webglGetLeftBracePos(name){return name.slice(-1)=="]"&&name.lastIndexOf("[")}function webglPrepareUniformLocationsBeforeFirstUse(program){var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex>2]}GLctx["invalidateFramebuffer"](target,list)}function _glLinkProgram(program){program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}}function emscriptenWebGLGetBufferBinding(target){switch(target){case 34962:target=34964;break;case 34963:target=34965;break;case 35051:target=35053;break;case 35052:target=35055;break;case 35982:target=35983;break;case 36662:target=36662;break;case 36663:target=36663;break;case 35345:target=35368;break}var buffer=GLctx.getParameter(target);if(buffer)return buffer.name|0;else return 0}function emscriptenWebGLValidateMapBufferTarget(target){switch(target){case 34962:case 34963:case 36662:case 36663:case 35051:case 35052:case 35882:case 35982:case 35345:return true;default:return false}}function _glMapBufferRange(target,offset,length,access){if(access!=26&&access!=10){err("glMapBufferRange is only supported when access is MAP_WRITE|INVALIDATE_BUFFER");return 0}if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glMapBufferRange");return 0}var mem=_malloc(length);if(!mem)return 0;GL.mappedBuffers[emscriptenWebGLGetBufferBinding(target)]={offset:offset,length:length,mem:mem,access:access};return mem}function _glPixelStorei(pname,param){if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)}function _glPolygonOffset(x0,x1){GLctx["polygonOffset"](x0,x1)}function _glProgramBinary(program,binaryFormat,binary,length){GL.recordError(1280)}function computeUnpackAlignedImageSize(width,height,sizePerPixel,alignment){function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=width*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,alignment);return height*alignedRowSize}function __colorChannelsInGlTextureFormat(format){var colorChannels={5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4};return colorChannels[format-6402]||1}function heapObjectForWebGLType(type){type-=5120;if(type==0)return HEAP8;if(type==1)return HEAPU8;if(type==2)return HEAP16;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922||type==28520||type==30779||type==30782)return HEAPU32;return HEAPU16}function heapAccessShiftForWebGLHeap(heap){return 31-Math.clz32(heap.BYTES_PER_ELEMENT)}function emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat){var heap=heapObjectForWebGLType(type);var shift=heapAccessShiftForWebGLHeap(heap);var byteSize=1<>shift,pixels+bytes>>shift)}function _glReadPixels(x,y,width,height,format,type,pixels){if(true){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels)}else{var heap=heapObjectForWebGLType(type);GLctx.readPixels(x,y,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}function _glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}function _glRenderbufferStorageMultisample(x0,x1,x2,x3,x4){GLctx["renderbufferStorageMultisample"](x0,x1,x2,x3,x4)}function _glSamplerParameterf(sampler,pname,param){GLctx["samplerParameterf"](GL.samplers[sampler],pname,param)}function _glSamplerParameteri(sampler,pname,param){GLctx["samplerParameteri"](GL.samplers[sampler],pname,param)}function _glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}function _glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}function _glStencilFuncSeparate(x0,x1,x2,x3){GLctx["stencilFuncSeparate"](x0,x1,x2,x3)}function _glStencilMaskSeparate(x0,x1){GLctx["stencilMaskSeparate"](x0,x1)}function _glStencilOpSeparate(x0,x1,x2,x3){GLctx["stencilOpSeparate"](x0,x1,x2,x3)}function _glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,null)}return}GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}function _glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}function _glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}function _glTexStorage2D(x0,x1,x2,x3,x4){GLctx["texStorage2D"](x0,x1,x2,x3,x4)}function _glTexStorage3D(x0,x1,x2,x3,x4,x5){GLctx["texStorage3D"](x0,x1,x2,x3,x4,x5)}function _glTexSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels){if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,null)}return}var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)}function _glTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels){if(GLctx.currentPixelUnpackBufferBinding){GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}}function webglGetUniformLocation(location){var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?"["+webglLoc+"]":""))}return webglLoc}else{GL.recordError(1282)}}function _glUniform1fv(location,count,value){count&&GLctx.uniform1fv(webglGetUniformLocation(location),HEAPF32,value>>2,count)}function _glUniform1i(location,v0){GLctx.uniform1i(webglGetUniformLocation(location),v0)}function _glUniform1iv(location,count,value){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count)}function _glUniform2fv(location,count,value){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2)}function _glUniform2iv(location,count,value){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2)}function _glUniform3fv(location,count,value){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3)}function _glUniform3iv(location,count,value){count&&GLctx.uniform3iv(webglGetUniformLocation(location),HEAP32,value>>2,count*3)}function _glUniform4fv(location,count,value){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4)}function _glUniform4iv(location,count,value){count&&GLctx.uniform4iv(webglGetUniformLocation(location),HEAP32,value>>2,count*4)}function _glUniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding){program=GL.programs[program];GLctx["uniformBlockBinding"](program,uniformBlockIndex,uniformBlockBinding)}function _glUniformMatrix3fv(location,count,transpose,value){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9)}function _glUniformMatrix4fv(location,count,transpose,value){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16)}function _glUnmapBuffer(target){if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glUnmapBuffer");return 0}var buffer=emscriptenWebGLGetBufferBinding(target);var mapping=GL.mappedBuffers[buffer];if(!mapping){GL.recordError(1282);err("buffer was never mapped in glUnmapBuffer");return 0}GL.mappedBuffers[buffer]=null;if(!(mapping.access&16))if(true){GLctx.bufferSubData(target,mapping.offset,HEAPU8,mapping.mem,mapping.length)}else{GLctx.bufferSubData(target,mapping.offset,HEAPU8.subarray(mapping.mem,mapping.mem+mapping.length))}_free(mapping.mem);return 1}function _glUseProgram(program){program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program}function _glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}function _glVertexAttribI4ui(x0,x1,x2,x3,x4){GLctx["vertexAttribI4ui"](x0,x1,x2,x3,x4)}function _glVertexAttribIPointer(index,size,type,stride,ptr){var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=false;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribIPointer(index,size,type,stride,ptr)};return}cb.clientside=false;GLctx["vertexAttribIPointer"](index,size,type,stride,ptr)}function _glVertexAttribPointer(index,size,type,normalized,stride,ptr){var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=normalized;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribPointer(index,size,type,normalized,stride,ptr)};return}cb.clientside=false;GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}function _glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}function _setTempRet0(val){setTempRet0(val)}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}else{return thisDate.getFullYear()}}else{return thisDate.getFullYear()-1}}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}else{return"PM"}},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm){return _strftime(s,maxsize,format,tm)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var asmLibraryArg={"Ha":___syscall_fcntl64,"Jb":___syscall_ioctl,"Kb":___syscall_openat,"Fb":___syscall_stat64,"y":__embind_finalize_value_array,"m":__embind_finalize_value_object,"zb":__embind_register_bigint,"Qb":__embind_register_bool,"e":__embind_register_class,"l":__embind_register_class_class_function,"o":__embind_register_class_constructor,"a":__embind_register_class_function,"x":__embind_register_class_property,"Pb":__embind_register_emval,"h":__embind_register_enum,"b":__embind_register_enum_value,"Ja":__embind_register_float,"_":__embind_register_function,"D":__embind_register_integer,"q":__embind_register_memory_view,"Ia":__embind_register_std_string,"na":__embind_register_std_wstring,"z":__embind_register_value_array,"f":__embind_register_value_array_element,"n":__embind_register_value_object,"d":__embind_register_value_object_field,"Rb":__embind_register_void,"Nb":__emscripten_date_now,"xb":__emscripten_err,"Mb":__emscripten_get_now_is_monotonic,"wb":__emscripten_out,"t":__emval_as,"g":__emval_decref,"u":__emval_get_property,"Z":__emval_incref,"H":__emval_new_cstring,"s":__emval_run_destructors,"v":__emval_take_value,"c":_abort,"oa":_emscripten_asm_const_int,"Eb":_emscripten_get_heap_max,"Lb":_emscripten_get_now,"Ob":_emscripten_memcpy_big,"Db":_emscripten_resize_heap,"Gb":_environ_get,"Hb":_environ_sizes_get,"ma":_fd_close,"Ib":_fd_read,"yb":_fd_seek,"Ga":_fd_write,"Bb":_getentropy,"i":_glActiveTexture,"la":_glAttachShader,"ib":_glBeginQuery,"qb":_glBindAttribLocation,"p":_glBindBuffer,"ua":_glBindBufferBase,"pa":_glBindBufferRange,"k":_glBindFramebuffer,"Oa":_glBindRenderbuffer,"ja":_glBindSampler,"j":_glBindTexture,"eb":_glBindVertexArray,"xa":_glBlendEquationSeparate,"wa":_glBlendFuncSeparate,"$":_glBlitFramebuffer,"F":_glBufferData,"aa":_glBufferSubData,"Vb":_glClear,"_b":_glClearBufferfi,"E":_glClearBufferfv,"Zb":_glClearBufferiv,"Yb":_glClearColor,"Xb":_glClearDepthf,"Wb":_glClearStencil,"Ab":_glClientWaitSync,"ga":_glColorMask,"rb":_glCompileShader,"La":_glCompressedTexSubImage2D,"Ka":_glCompressedTexSubImage3D,"kc":_glCopyBufferSubData,"Ea":_glCreateProgram,"tb":_glCreateShader,"ya":_glCullFace,"ia":_glDeleteBuffers,"S":_glDeleteFramebuffers,"X":_glDeleteProgram,"hb":_glDeleteQueries,"Sa":_glDeleteRenderbuffers,"Aa":_glDeleteSamplers,"P":_glDeleteShader,"Qa":_glDeleteSync,"Ta":_glDeleteTextures,"fb":_glDeleteVertexArrays,"ka":_glDepthFunc,"fa":_glDepthMask,"ra":_glDepthRangef,"Q":_glDetachShader,"r":_glDisable,"bc":_glDisableVertexAttribArray,"nc":_glDrawBuffers,"jc":_glDrawElements,"ic":_glDrawElementsInstanced,"A":_glEnable,"ec":_glEnableVertexAttribArray,"jb":_glEndQuery,"ba":_glFenceSync,"Da":_glFinish,"mb":_glFlush,"I":_glFramebufferRenderbuffer,"C":_glFramebufferTexture2D,"M":_glFramebufferTextureLayer,"za":_glFrontFace,"U":_glGenBuffers,"ca":_glGenFramebuffers,"gb":_glGenQueries,"ea":_glGenRenderbuffers,"Ca":_glGenSamplers,"T":_glGenTextures,"db":_glGenVertexArrays,"lc":_glGenerateMipmap,"Ub":_glGetBufferSubData,"Y":_glGetError,"cb":_glGetFloatv,"B":_glGetIntegerv,"ub":_glGetProgramBinary,"nb":_glGetProgramInfoLog,"R":_glGetProgramiv,"kb":_glGetQueryObjectuiv,"ob":_glGetShaderInfoLog,"O":_glGetShaderiv,"J":_glGetString,"ab":_glGetUniformBlockIndex,"da":_glGetUniformLocation,"bb":_glHint,"lb":_glInvalidateFramebuffer,"pb":_glLinkProgram,"Tb":_glMapBufferRange,"N":_glPixelStorei,"va":_glPolygonOffset,"vb":_glProgramBinary,"Ra":_glReadPixels,"$b":_glRenderbufferStorage,"ac":_glRenderbufferStorageMultisample,"Ba":_glSamplerParameterf,"K":_glSamplerParameteri,"ta":_glScissor,"sb":_glShaderSource,"W":_glStencilFuncSeparate,"G":_glStencilMaskSeparate,"V":_glStencilOpSeparate,"L":_glTexImage2D,"mc":_glTexParameterf,"w":_glTexParameteri,"hc":_glTexStorage2D,"Pa":_glTexStorage3D,"Na":_glTexSubImage2D,"Ma":_glTexSubImage3D,"_a":_glUniform1fv,"qa":_glUniform1i,"Wa":_glUniform1iv,"Za":_glUniform2fv,"Va":_glUniform2iv,"Ya":_glUniform3fv,"Ua":_glUniform3iv,"Xa":_glUniform4fv,"qc":_glUniform4iv,"$a":_glUniformBlockBinding,"pc":_glUniformMatrix3fv,"oc":_glUniformMatrix4fv,"Sb":_glUnmapBuffer,"ha":_glUseProgram,"cc":_glVertexAttrib4f,"dc":_glVertexAttribI4ui,"gc":_glVertexAttribIPointer,"fc":_glVertexAttribPointer,"sa":_glViewport,"Fa":_setTempRet0,"Cb":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["sc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["uc"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["vc"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["wc"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["xc"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["yc"]).apply(null,arguments)};var dynCall_iiiiij=Module["dynCall_iiiiij"]=function(){return(dynCall_iiiiij=Module["dynCall_iiiiij"]=Module["asm"]["zc"]).apply(null,arguments)};var dynCall_jii=Module["dynCall_jii"]=function(){return(dynCall_jii=Module["dynCall_jii"]=Module["asm"]["Ac"]).apply(null,arguments)};var dynCall_iiij=Module["dynCall_iiij"]=function(){return(dynCall_iiij=Module["dynCall_iiij"]=Module["asm"]["Bc"]).apply(null,arguments)};var dynCall_iiiij=Module["dynCall_iiiij"]=function(){return(dynCall_iiiij=Module["dynCall_iiiij"]=Module["asm"]["Cc"]).apply(null,arguments)};var dynCall_vij=Module["dynCall_vij"]=function(){return(dynCall_vij=Module["dynCall_vij"]=Module["asm"]["Dc"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["Ec"]).apply(null,arguments)};var dynCall_viijii=Module["dynCall_viijii"]=function(){return(dynCall_viijii=Module["dynCall_viijii"]=Module["asm"]["Fc"]).apply(null,arguments)};var dynCall_iiiiijj=Module["dynCall_iiiiijj"]=function(){return(dynCall_iiiiijj=Module["dynCall_iiiiijj"]=Module["asm"]["Gc"]).apply(null,arguments)};var dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=function(){return(dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=Module["asm"]["Hc"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); return Filament.ready @@ -214,6 +214,7 @@ Filament.loadGeneratedExtensions = function() { Filament.View.prototype.setDepthOfFieldOptionsDefaults = function(overrides) { const options = { cocScale: 1.0, + cocAspectRatio: 1.0, maxApertureDiameter: 0.01, enabled: false, filter: Filament.View$DepthOfFieldOptions$Filter.MEDIAN, @@ -292,8 +293,20 @@ Filament.loadGeneratedExtensions = function() { Filament.View.prototype.setTemporalAntiAliasingOptionsDefaults = function(overrides) { const options = { filterWidth: 1.0, - feedback: 0.04, + feedback: 0.12, + lodBias: -1.0, + sharpness: 0.0, enabled: false, + upscaling: false, + filterHistory: true, + filterInput: true, + useYCoCg: false, + boxType: Filament.View$TemporalAntiAliasingOptions$BoxType.AABB, + boxClipping: Filament.View$TemporalAntiAliasingOptions$BoxClipping.ACCURATE, + jitterPattern: Filament.View$TemporalAntiAliasingOptions$JitterPattern.HALTON_23_X16, + varianceGamma: 1.0, + preventFlickering: false, + historyReprojection: true, }; return Object.assign(options, overrides); }; @@ -686,6 +699,12 @@ Filament.loadClassExtensions = function() { this._setGuardBandOptions(options); }; + /// setStereoscopicOptions ::method:: + Filament.View.prototype.setStereoscopicOptions = function(overrides) { + const options = this.setStereoscopicOptionsDefaults(overrides); + this._setStereoscopicOptions(options); + } + /// BufferObject ::core class:: /// setBuffer ::method:: diff --git a/docs/remote/filament.wasm b/docs/remote/filament.wasm index 7ba7e66d27f..758b479d09a 100755 Binary files a/docs/remote/filament.wasm and b/docs/remote/filament.wasm differ diff --git a/docs/webgl/filament.js b/docs/webgl/filament.js index 8fbaf161c5c..c045339d7cb 100644 --- a/docs/webgl/filament.js +++ b/docs/webgl/filament.js @@ -3,22 +3,20 @@ var Filament = (() => { var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; return ( -function(Filament) { - Filament = Filament || {}; +function(moduleArg = {}) { -var Module=typeof Filament!="undefined"?Filament:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}var fs;var nodePath;var requireNodeFS;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}requireNodeFS=()=>{if(!nodePath){fs=require("fs");nodePath=require("path")}};read_=function shell_read(filename,binary){requireNodeFS();filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{requireNodeFS();filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var tempRet0=0;var setTempRet0=value=>{tempRet0=value};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="filament.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["rc"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["tc"];addOnInit(Module["asm"]["sc"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;var ASM_CONSTS={1306044:()=>{const options=window.filament_glOptions;const context=window.filament_glContext;const handle=GL.registerContext(context,options);window.filament_contextHandle=handle;GL.makeContextCurrent(handle)},1306258:()=>{const handle=window.filament_contextHandle;GL.makeContextCurrent(handle)},1306339:($0,$1,$2,$3,$4,$5)=>{const fn=Emval.toValue($0);fn({"renderable":Emval.toValue($1),"depth":$2,"fragCoords":[$3,$4,$5]})}};function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{path=PATH_FS.resolve(FS.cwd(),path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(p=>!!p),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream||!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(ret.exists){return ret.object}else{return null}},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=FS.getStream(dirfd);if(!dirstream)throw new FS.ErrnoError(8);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}return name}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}}function __embind_finalize_value_array(rawTupleType){var reg=tupleRegistrations[rawTupleType];delete tupleRegistrations[rawTupleType];var elements=reg.elements;var elementsLength=elements.length;var elementTypes=elements.map(function(elt){return elt.getterReturnType}).concat(elements.map(function(elt){return elt.setterArgumentType}));var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;whenDependentTypesAreResolved([rawTupleType],elementTypes,function(elementTypes){elements.forEach((elt,i)=>{var getterReturnType=elementTypes[i];var getter=elt.getter;var getterContext=elt.getterContext;var setterArgumentType=elementTypes[i+elementsLength];var setter=elt.setter;var setterContext=elt.setterContext;elt.read=ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))};elt.write=(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}});return[{name:reg.name,"fromWireType":function(ptr){var rv=new Array(elementsLength);for(var i=0;ifield.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))},write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":function(ptr){var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":function(destructors,o){for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError('Missing field: "'+fieldName+'"')}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}var name=registeredInstance.name;if(!rawType){throwBindingError('type "'+name+'" must have a positive integer typeid pointer')}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError("Cannot register type '"+name+"' twice")}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function __embind_register_bool(rawType,name,size,trueValue,falseValue){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":8,"readValueFromPointer":function(pointer){var heap;if(size===1){heap=HEAP8}else if(size===2){heap=HEAP16}else if(size===4){heap=HEAP32}else{throw new TypeError("Unknown boolean type size: "+name)}return this["fromWireType"](heap[pointer>>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationRegistry=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}var registeredPointers={};function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}var delayFunction=undefined;function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function attachFinalizer(handle){if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError("unknown function pointer with signature "+signature+": "+rawFunction)}return fp}var UnboundTypeError=undefined;function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_class(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor){name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);if(upcast){upcast=embind__requireFunction(upcastSignature,upcast)}if(downcast){downcast=embind__requireFunction(downcastSignature,downcast)}rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError("Cannot construct "+name+" due to unbound types",[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],function(base){base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(legalFunctionName,function(){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[arguments.length];if(undefined===body){throw new BindingError("Tried to invoke ctor of "+name+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(registeredClass.constructor_body).toString()+") parameters instead!")}return body.apply(this,arguments)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})}function new_(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError("new_ called with constructor type "+typeof constructor+" which is not a function")}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})}function __embind_register_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})}function validateThis(this_,classType,humanName){if(!(this_ instanceof Object)){throwBindingError(humanName+' with invalid "this": '+this_)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(humanName+' incompatible with "this" of type '+this_.constructor.name)}if(!this_.$$.ptr){throwBindingError("cannot call emscripten binding method "+humanName+" on deleted object")}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)}function __embind_register_class_property(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){fieldName=readLatin1String(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],function(classType){classType=classType[0];var humanName=classType.name+"."+fieldName;var desc={get:function(){throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>{throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])}}else{desc.set=v=>{throwBindingError(humanName+" is a read-only property")}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],function(types){var getterReturnType=types[0];var desc={get:function(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType["fromWireType"](getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})}var emval_free_list=[];var emval_handle_array=[{},{value:undefined},{value:null},{value:true},{value:false}];function __emval_decref(handle){if(handle>4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{var handle=emval_free_list.length?emval_free_list.pop():emval_handle_array.length;emval_handle_array[handle]={refcount:1,value:value};return handle}}}};function __embind_register_emval(rawType,name){name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(handle){var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":function(destructors,value){return Emval.toHandle(value)},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})}function enumReadValueFromPointer(name,shift,signed){switch(shift){case 0:return function(pointer){var heap=signed?HEAP8:HEAPU8;return this["fromWireType"](heap[pointer])};case 1:return function(pointer){var heap=signed?HEAP16:HEAPU16;return this["fromWireType"](heap[pointer>>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function embindRepr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function __embind_register_function(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn){var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError("Cannot call "+name+" due to unbound types",argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn),argCount-1);return[]})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;iHEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value=="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function __emscripten_date_now(){return Date.now()}function __emscripten_err(str){err(UTF8ToString(str))}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function __emscripten_out(str){out(UTF8ToString(str))}function __emval_as(handle,returnType,destructorsRef){handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=Emval.toHandle(destructors);HEAPU32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_get_property(handle,key){handle=Emval.toValue(handle);key=Emval.toValue(key);return Emval.toHandle(handle[key])}function __emval_incref(handle){if(handle>4){emval_handle_array[handle].refcount+=1}}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol}function __emval_new_cstring(v){return Emval.toHandle(getStringOrSymbol(v))}function __emval_run_destructors(handle){var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)}function __emval_take_value(type,arg){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)}function _abort(){abort("")}var readAsmConstArgsArray=[];function readAsmConstArgs(sigPtr,buf){readAsmConstArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readAsmConstArgsArray.push(ch==105?HEAP32[buf]:HEAPF64[buf++>>1]);++buf}return readAsmConstArgsArray}function _emscripten_asm_const_int(code,sigPtr,argbuf){var args=readAsmConstArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}function getHeapMax(){return 2147483648}function _emscripten_get_heap_max(){return getHeapMax()}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=()=>{var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else _emscripten_get_now=()=>performance.now();function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _getentropy(buffer,size){if(!_getentropy.randomDevice){_getentropy.randomDevice=getRandomDevice()}for(var i=0;i>0]=_getentropy.randomDevice()}return 0}function __webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw(ctx){return!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"))}var GL={counter:1,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],byteSizeByTypeRoot:5120,byteSizeByType:[1,1,2,2,4,4,4,2,3,4,8],stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i>1;var quadIndexes=new Uint16Array(numIndexes);var i=0,v=0;while(1){quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+1;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v+3;if(i>=numIndexes)break;v+=4}context.GLctx.bufferData(34963,quadIndexes,35044);context.GLctx.bindBuffer(34963,null)}},getTempVertexBuffer:function getTempVertexBuffer(sizeBytes){var idx=GL.log2ceilLookup(sizeBytes);var ringbuffer=GL.currentContext.tempVertexBuffers1[idx];var nextFreeBufferIndex=GL.currentContext.tempVertexBufferCounters1[idx];GL.currentContext.tempVertexBufferCounters1[idx]=GL.currentContext.tempVertexBufferCounters1[idx]+1&GL.numTempVertexBuffersPerSize-1;var vbo=ringbuffer[nextFreeBufferIndex];if(vbo){return vbo}var prevVBO=GLctx.getParameter(34964);ringbuffer[nextFreeBufferIndex]=GLctx.createBuffer();GLctx.bindBuffer(34962,ringbuffer[nextFreeBufferIndex]);GLctx.bufferData(34962,1<>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},calcBufLength:function calcBufLength(size,type,stride,count){if(stride>0){return count*stride}var typeSize=GL.byteSizeByType[type-GL.byteSizeByTypeRoot];return size*typeSize*count},usedTempBuffers:[],preDrawHandleClientVertexAttribBindings:function preDrawHandleClientVertexAttribBindings(count){GL.resetBufferBinding=false;for(var i=0;i=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}__webgl_enable_WEBGL_multi_draw(GLctx);var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}})}};function _glActiveTexture(x0){GLctx["activeTexture"](x0)}function _glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}function _glBeginQuery(target,id){GLctx["beginQuery"](target,GL.queries[id])}function _glBindAttribLocation(program,index,name){GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))}function _glBindBuffer(target,buffer){if(target==34962){GLctx.currentArrayBufferBinding=buffer}else if(target==34963){GLctx.currentElementArrayBufferBinding=buffer}if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])}function _glBindBufferBase(target,index,buffer){GLctx["bindBufferBase"](target,index,GL.buffers[buffer])}function _glBindBufferRange(target,index,buffer,offset,ptrsize){GLctx["bindBufferRange"](target,index,GL.buffers[buffer],offset,ptrsize)}function _glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])}function _glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}function _glBindSampler(unit,sampler){GLctx["bindSampler"](unit,GL.samplers[sampler])}function _glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}function _glBindVertexArray(vao){GLctx["bindVertexArray"](GL.vaos[vao]);var ibo=GLctx.getParameter(34965);GLctx.currentElementArrayBufferBinding=ibo?ibo.name|0:0}function _glBlendEquationSeparate(x0,x1){GLctx["blendEquationSeparate"](x0,x1)}function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}function _glBlitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9){GLctx["blitFramebuffer"](x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)}function _glBufferData(target,size,data,usage){if(true){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}}else{GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}}function _glBufferSubData(target,offset,size,data){if(true){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}function _glClear(x0){GLctx["clear"](x0)}function _glClearBufferfi(x0,x1,x2,x3){GLctx["clearBufferfi"](x0,x1,x2,x3)}function _glClearBufferfv(buffer,drawbuffer,value){GLctx["clearBufferfv"](buffer,drawbuffer,HEAPF32,value>>2)}function _glClearBufferiv(buffer,drawbuffer,value){GLctx["clearBufferiv"](buffer,drawbuffer,HEAP32,value>>2)}function _glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}function _glClearDepthf(x0){GLctx["clearDepth"](x0)}function _glClearStencil(x0){GLctx["clearStencil"](x0)}function convertI32PairToI53(lo,hi){return(lo>>>0)+hi*4294967296}function _glClientWaitSync(sync,flags,timeoutLo,timeoutHi){return GLctx.clientWaitSync(GL.syncs[sync],flags,convertI32PairToI53(timeoutLo,timeoutHi))}function _glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}function _glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}function _glCompressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data){if(true){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,imageSize,data)}else{GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,HEAPU8,data,imageSize)}return}GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,data?HEAPU8.subarray(data,data+imageSize):null)}function _glCompressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data){if(GLctx.currentPixelUnpackBufferBinding){GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}}function _glCopyBufferSubData(x0,x1,x2,x3,x4){GLctx["copyBufferSubData"](x0,x1,x2,x3,x4)}function _glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id}function _glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}function _glCullFace(x0){GLctx["cullFace"](x0)}function _glDeleteBuffers(n,buffers){for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentArrayBufferBinding)GLctx.currentArrayBufferBinding=0;if(id==GLctx.currentElementArrayBufferBinding)GLctx.currentElementArrayBufferBinding=0;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}}function _glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}function _glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null}function _glDeleteQueries(n,ids){for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx["deleteQuery"](query);GL.queries[id]=null}}function _glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}function _glDeleteSamplers(n,samplers){for(var i=0;i>2];var sampler=GL.samplers[id];if(!sampler)continue;GLctx["deleteSampler"](sampler);sampler.name=0;GL.samplers[id]=null}}function _glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}function _glDeleteSync(id){if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null}function _glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}function _glDeleteVertexArrays(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}function _glDepthFunc(x0){GLctx["depthFunc"](x0)}function _glDepthMask(flag){GLctx.depthMask(!!flag)}function _glDepthRangef(x0,x1){GLctx["depthRange"](x0,x1)}function _glDetachShader(program,shader){GLctx.detachShader(GL.programs[program],GL.shaders[shader])}function _glDisable(x0){GLctx["disable"](x0)}function _glDisableVertexAttribArray(index){var cb=GL.currentContext.clientBuffers[index];cb.enabled=false;GLctx.disableVertexAttribArray(index)}var tempFixedLengthArray=[];function _glDrawBuffers(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}function _glDrawElements(mode,count,type,indices){var buf;if(!GLctx.currentElementArrayBufferBinding){var size=GL.calcBufLength(1,type,0,count);buf=GL.getTempIndexBuffer(size);GLctx.bindBuffer(34963,buf);GLctx.bufferSubData(34963,0,HEAPU8.subarray(indices,indices+size));indices=0}GL.preDrawHandleClientVertexAttribBindings(count);GLctx.drawElements(mode,count,type,indices);GL.postDrawHandleClientVertexAttribBindings(count);if(!GLctx.currentElementArrayBufferBinding){GLctx.bindBuffer(34963,null)}}function _glDrawElementsInstanced(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}function _glEnable(x0){GLctx["enable"](x0)}function _glEnableVertexAttribArray(index){var cb=GL.currentContext.clientBuffers[index];cb.enabled=true;GLctx.enableVertexAttribArray(index)}function _glEndQuery(x0){GLctx["endQuery"](x0)}function _glFenceSync(condition,flags){var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}else{return 0}}function _glFinish(){GLctx["finish"]()}function _glFlush(){GLctx["flush"]()}function _glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}function _glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}function _glFramebufferTextureLayer(target,attachment,texture,level,layer){GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)}function _glFrontFace(x0){GLctx["frontFace"](x0)}function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i>2]=id}}function _glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}function _glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}function _glGenQueries(n,ids){__glGenObject(n,ids,"createQuery",GL.queries)}function _glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}function _glGenSamplers(n,samplers){__glGenObject(n,samplers,"createSampler",GL.samplers)}function _glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}function _glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}function _glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}function _glGetBufferSubData(target,offset,size,data){if(!data){GL.recordError(1281);return}size&&GLctx["getBufferSubData"](target,offset,HEAPU8,data,size)}function _glGetError(){var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error}function writeI53ToI64(ptr,num){HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}var exts=GLctx.getSupportedExtensions()||[];ret=2*exts.length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}function _glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}function _glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}function _glGetProgramBinary(program,bufSize,length,binaryFormat,binary){GL.recordError(1282)}function _glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}}function _glGetQueryObjectuiv(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx["getQueryParameter"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}function _glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}function stringToNewUTF8(jsString){var length=lengthBytesUTF8(jsString)+1;var cString=_malloc(length);stringToUTF8(jsString,cString,length);return cString}function _glGetString(name_){var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);if(true)glVersion="OpenGL ES 3.0 ("+glVersion+")";else{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret}function _glGetUniformBlockIndex(program,uniformBlockName){return GLctx["getUniformBlockIndex"](GL.programs[program],UTF8ToString(uniformBlockName))}function jstoi_q(str){return parseInt(str)}function webglGetLeftBracePos(name){return name.slice(-1)=="]"&&name.lastIndexOf("[")}function webglPrepareUniformLocationsBeforeFirstUse(program){var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex>2]}GLctx["invalidateFramebuffer"](target,list)}function _glLinkProgram(program){program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}}function emscriptenWebGLGetBufferBinding(target){switch(target){case 34962:target=34964;break;case 34963:target=34965;break;case 35051:target=35053;break;case 35052:target=35055;break;case 35982:target=35983;break;case 36662:target=36662;break;case 36663:target=36663;break;case 35345:target=35368;break}var buffer=GLctx.getParameter(target);if(buffer)return buffer.name|0;else return 0}function emscriptenWebGLValidateMapBufferTarget(target){switch(target){case 34962:case 34963:case 36662:case 36663:case 35051:case 35052:case 35882:case 35982:case 35345:return true;default:return false}}function _glMapBufferRange(target,offset,length,access){if(access!=26&&access!=10){err("glMapBufferRange is only supported when access is MAP_WRITE|INVALIDATE_BUFFER");return 0}if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glMapBufferRange");return 0}var mem=_malloc(length);if(!mem)return 0;GL.mappedBuffers[emscriptenWebGLGetBufferBinding(target)]={offset:offset,length:length,mem:mem,access:access};return mem}function _glPixelStorei(pname,param){if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)}function _glPolygonOffset(x0,x1){GLctx["polygonOffset"](x0,x1)}function _glProgramBinary(program,binaryFormat,binary,length){GL.recordError(1280)}function computeUnpackAlignedImageSize(width,height,sizePerPixel,alignment){function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=width*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,alignment);return height*alignedRowSize}function __colorChannelsInGlTextureFormat(format){var colorChannels={5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4};return colorChannels[format-6402]||1}function heapObjectForWebGLType(type){type-=5120;if(type==0)return HEAP8;if(type==1)return HEAPU8;if(type==2)return HEAP16;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922||type==28520||type==30779||type==30782)return HEAPU32;return HEAPU16}function heapAccessShiftForWebGLHeap(heap){return 31-Math.clz32(heap.BYTES_PER_ELEMENT)}function emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat){var heap=heapObjectForWebGLType(type);var shift=heapAccessShiftForWebGLHeap(heap);var byteSize=1<>shift,pixels+bytes>>shift)}function _glReadPixels(x,y,width,height,format,type,pixels){if(true){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels)}else{var heap=heapObjectForWebGLType(type);GLctx.readPixels(x,y,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}function _glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}function _glRenderbufferStorageMultisample(x0,x1,x2,x3,x4){GLctx["renderbufferStorageMultisample"](x0,x1,x2,x3,x4)}function _glSamplerParameterf(sampler,pname,param){GLctx["samplerParameterf"](GL.samplers[sampler],pname,param)}function _glSamplerParameteri(sampler,pname,param){GLctx["samplerParameteri"](GL.samplers[sampler],pname,param)}function _glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}function _glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}function _glStencilFuncSeparate(x0,x1,x2,x3){GLctx["stencilFuncSeparate"](x0,x1,x2,x3)}function _glStencilMaskSeparate(x0,x1){GLctx["stencilMaskSeparate"](x0,x1)}function _glStencilOpSeparate(x0,x1,x2,x3){GLctx["stencilOpSeparate"](x0,x1,x2,x3)}function _glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,null)}return}GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}function _glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}function _glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}function _glTexStorage2D(x0,x1,x2,x3,x4){GLctx["texStorage2D"](x0,x1,x2,x3,x4)}function _glTexStorage3D(x0,x1,x2,x3,x4,x5){GLctx["texStorage3D"](x0,x1,x2,x3,x4,x5)}function _glTexSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels){if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,null)}return}var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)}function _glTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels){if(GLctx.currentPixelUnpackBufferBinding){GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}}function webglGetUniformLocation(location){var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?"["+webglLoc+"]":""))}return webglLoc}else{GL.recordError(1282)}}function _glUniform1fv(location,count,value){count&&GLctx.uniform1fv(webglGetUniformLocation(location),HEAPF32,value>>2,count)}function _glUniform1i(location,v0){GLctx.uniform1i(webglGetUniformLocation(location),v0)}function _glUniform1iv(location,count,value){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count)}function _glUniform2fv(location,count,value){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2)}function _glUniform2iv(location,count,value){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2)}function _glUniform3fv(location,count,value){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3)}function _glUniform3iv(location,count,value){count&&GLctx.uniform3iv(webglGetUniformLocation(location),HEAP32,value>>2,count*3)}function _glUniform4fv(location,count,value){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4)}function _glUniform4iv(location,count,value){count&&GLctx.uniform4iv(webglGetUniformLocation(location),HEAP32,value>>2,count*4)}function _glUniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding){program=GL.programs[program];GLctx["uniformBlockBinding"](program,uniformBlockIndex,uniformBlockBinding)}function _glUniformMatrix3fv(location,count,transpose,value){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9)}function _glUniformMatrix4fv(location,count,transpose,value){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16)}function _glUnmapBuffer(target){if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glUnmapBuffer");return 0}var buffer=emscriptenWebGLGetBufferBinding(target);var mapping=GL.mappedBuffers[buffer];if(!mapping){GL.recordError(1282);err("buffer was never mapped in glUnmapBuffer");return 0}GL.mappedBuffers[buffer]=null;if(!(mapping.access&16))if(true){GLctx.bufferSubData(target,mapping.offset,HEAPU8,mapping.mem,mapping.length)}else{GLctx.bufferSubData(target,mapping.offset,HEAPU8.subarray(mapping.mem,mapping.mem+mapping.length))}_free(mapping.mem);return 1}function _glUseProgram(program){program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program}function _glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}function _glVertexAttribI4ui(x0,x1,x2,x3,x4){GLctx["vertexAttribI4ui"](x0,x1,x2,x3,x4)}function _glVertexAttribIPointer(index,size,type,stride,ptr){var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=false;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribIPointer(index,size,type,stride,ptr)};return}cb.clientside=false;GLctx["vertexAttribIPointer"](index,size,type,stride,ptr)}function _glVertexAttribPointer(index,size,type,normalized,stride,ptr){var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=normalized;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribPointer(index,size,type,normalized,stride,ptr)};return}cb.clientside=false;GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}function _glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}function _setTempRet0(val){setTempRet0(val)}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}else{return thisDate.getFullYear()}}else{return thisDate.getFullYear()-1}}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}else{return"PM"}},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm){return _strftime(s,maxsize,format,tm)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var asmLibraryArg={"Ea":___syscall_fcntl64,"Jb":___syscall_ioctl,"Kb":___syscall_openat,"Fb":___syscall_stat64,"y":__embind_finalize_value_array,"l":__embind_finalize_value_object,"zb":__embind_register_bigint,"Qb":__embind_register_bool,"e":__embind_register_class,"k":__embind_register_class_class_function,"n":__embind_register_class_constructor,"a":__embind_register_class_function,"x":__embind_register_class_property,"Pb":__embind_register_emval,"h":__embind_register_enum,"b":__embind_register_enum_value,"Ga":__embind_register_float,"X":__embind_register_function,"C":__embind_register_integer,"q":__embind_register_memory_view,"Fa":__embind_register_std_string,"ia":__embind_register_std_wstring,"z":__embind_register_value_array,"f":__embind_register_value_array_element,"m":__embind_register_value_object,"d":__embind_register_value_object_field,"Rb":__embind_register_void,"Nb":__emscripten_date_now,"xb":__emscripten_err,"Mb":__emscripten_get_now_is_monotonic,"Ba":__emscripten_out,"s":__emval_as,"g":__emval_decref,"t":__emval_get_property,"W":__emval_incref,"G":__emval_new_cstring,"r":__emval_run_destructors,"u":__emval_take_value,"c":_abort,"ja":_emscripten_asm_const_int,"Eb":_emscripten_get_heap_max,"Lb":_emscripten_get_now,"Ob":_emscripten_memcpy_big,"Db":_emscripten_resize_heap,"Gb":_environ_get,"Hb":_environ_sizes_get,"ha":_fd_close,"Ib":_fd_read,"yb":_fd_seek,"Da":_fd_write,"Bb":_getentropy,"i":_glActiveTexture,"ga":_glAttachShader,"jb":_glBeginQuery,"rb":_glBindAttribLocation,"p":_glBindBuffer,"qa":_glBindBufferBase,"ka":_glBindBufferRange,"o":_glBindFramebuffer,"La":_glBindRenderbuffer,"ea":_glBindSampler,"j":_glBindTexture,"fb":_glBindVertexArray,"ta":_glBlendEquationSeparate,"sa":_glBlendFuncSeparate,"Qa":_glBlitFramebuffer,"E":_glBufferData,"Y":_glBufferSubData,"Vb":_glClear,"_b":_glClearBufferfi,"D":_glClearBufferfv,"Zb":_glClearBufferiv,"Yb":_glClearColor,"Xb":_glClearDepthf,"Wb":_glClearStencil,"Ab":_glClientWaitSync,"ba":_glColorMask,"sb":_glCompileShader,"Ia":_glCompressedTexSubImage2D,"Ha":_glCompressedTexSubImage3D,"kc":_glCopyBufferSubData,"Aa":_glCreateProgram,"ub":_glCreateShader,"ua":_glCullFace,"da":_glDeleteBuffers,"la":_glDeleteFramebuffers,"U":_glDeleteProgram,"ib":_glDeleteQueries,"Sa":_glDeleteRenderbuffers,"wa":_glDeleteSamplers,"M":_glDeleteShader,"Pa":_glDeleteSync,"Ta":_glDeleteTextures,"gb":_glDeleteVertexArrays,"fa":_glDepthFunc,"aa":_glDepthMask,"na":_glDepthRangef,"N":_glDetachShader,"v":_glDisable,"bc":_glDisableVertexAttribArray,"nc":_glDrawBuffers,"jc":_glDrawElements,"ic":_glDrawElementsInstanced,"A":_glEnable,"ec":_glEnableVertexAttribArray,"kb":_glEndQuery,"Z":_glFenceSync,"za":_glFinish,"nb":_glFlush,"P":_glFramebufferRenderbuffer,"Na":_glFramebufferTexture2D,"Ma":_glFramebufferTextureLayer,"va":_glFrontFace,"R":_glGenBuffers,"Ua":_glGenFramebuffers,"hb":_glGenQueries,"$":_glGenRenderbuffers,"ya":_glGenSamplers,"Q":_glGenTextures,"mb":_glGenVertexArrays,"lc":_glGenerateMipmap,"Ub":_glGetBufferSubData,"V":_glGetError,"db":_glGetFloatv,"B":_glGetIntegerv,"vb":_glGetProgramBinary,"ob":_glGetProgramInfoLog,"O":_glGetProgramiv,"lb":_glGetQueryObjectuiv,"pb":_glGetShaderInfoLog,"L":_glGetShaderiv,"H":_glGetString,"bb":_glGetUniformBlockIndex,"_":_glGetUniformLocation,"cb":_glHint,"eb":_glInvalidateFramebuffer,"qb":_glLinkProgram,"Tb":_glMapBufferRange,"K":_glPixelStorei,"ra":_glPolygonOffset,"wb":_glProgramBinary,"Ra":_glReadPixels,"$b":_glRenderbufferStorage,"ac":_glRenderbufferStorageMultisample,"xa":_glSamplerParameterf,"I":_glSamplerParameteri,"pa":_glScissor,"tb":_glShaderSource,"T":_glStencilFuncSeparate,"F":_glStencilMaskSeparate,"S":_glStencilOpSeparate,"J":_glTexImage2D,"mc":_glTexParameterf,"w":_glTexParameteri,"hc":_glTexStorage2D,"Oa":_glTexStorage3D,"Ka":_glTexSubImage2D,"Ja":_glTexSubImage3D,"$a":_glUniform1fv,"ma":_glUniform1i,"Xa":_glUniform1iv,"_a":_glUniform2fv,"Wa":_glUniform2iv,"Za":_glUniform3fv,"Va":_glUniform3iv,"Ya":_glUniform4fv,"qc":_glUniform4iv,"ab":_glUniformBlockBinding,"pc":_glUniformMatrix3fv,"oc":_glUniformMatrix4fv,"Sb":_glUnmapBuffer,"ca":_glUseProgram,"cc":_glVertexAttrib4f,"dc":_glVertexAttribI4ui,"gc":_glVertexAttribIPointer,"fc":_glVertexAttribPointer,"oa":_glViewport,"Ca":_setTempRet0,"Cb":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["sc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["uc"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["vc"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["wc"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["xc"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["yc"]).apply(null,arguments)};var dynCall_iiiiij=Module["dynCall_iiiiij"]=function(){return(dynCall_iiiiij=Module["dynCall_iiiiij"]=Module["asm"]["zc"]).apply(null,arguments)};var dynCall_jii=Module["dynCall_jii"]=function(){return(dynCall_jii=Module["dynCall_jii"]=Module["asm"]["Ac"]).apply(null,arguments)};var dynCall_iiij=Module["dynCall_iiij"]=function(){return(dynCall_iiij=Module["dynCall_iiij"]=Module["asm"]["Bc"]).apply(null,arguments)};var dynCall_iiiij=Module["dynCall_iiiij"]=function(){return(dynCall_iiiij=Module["dynCall_iiiij"]=Module["asm"]["Cc"]).apply(null,arguments)};var dynCall_vij=Module["dynCall_vij"]=function(){return(dynCall_vij=Module["dynCall_vij"]=Module["asm"]["Dc"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["Ec"]).apply(null,arguments)};var dynCall_viijii=Module["dynCall_viijii"]=function(){return(dynCall_viijii=Module["dynCall_viijii"]=Module["asm"]["Fc"]).apply(null,arguments)};var dynCall_iiiiijj=Module["dynCall_iiiiijj"]=function(){return(dynCall_iiiiijj=Module["dynCall_iiiiijj"]=Module["asm"]["Gc"]).apply(null,arguments)};var dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=function(){return(dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=Module["asm"]["Hc"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); +var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");if(ENVIRONMENT_IS_WORKER){scriptDirectory=nodePath.dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)onerror(err);else onload(binary?data.buffer:data)})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow};Module["inspect"]=()=>"[Emscripten Module object]"}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="filament.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(binaryFile)){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{if(!response["ok"]){throw"failed to load wasm binary file at '"+binaryFile+"'"}return response["arrayBuffer"]()}).catch(()=>getBinarySync(binaryFile))}else if(readAsync){return new Promise((resolve,reject)=>{readAsync(binaryFile,response=>resolve(new Uint8Array(response)),reject)})}}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(instance=>instance).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["qc"];updateMemoryViews();wasmTable=wasmExports["sc"];addOnInit(wasmExports["rc"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var tempDouble;var tempI64;var ASM_CONSTS={1403596:()=>{const options=window.filament_glOptions;const context=window.filament_glContext;const handle=GL.registerContext(context,options);window.filament_contextHandle=handle;GL.makeContextCurrent(handle)},1403810:()=>{const handle=window.filament_contextHandle;GL.makeContextCurrent(handle)},1403891:($0,$1,$2,$3,$4,$5)=>{const fn=Emval.toValue($0);fn({"renderable":Emval.toValue($1),"depth":$2,"fragCoords":[$3,$4,$5]})}};var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var setErrNo=value=>{HEAP32[___errno_location()>>2]=value;return value};var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");var randomFillSync=crypto_module["randomFillSync"];if(randomFillSync){return view=>crypto_module["randomFillSync"](view)}var randomBytes=crypto_module["randomBytes"];return view=>(view.set(randomBytes(view.byteLength)),view)}catch(e){}}abort("initRandomDevice")};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};var mmapAlloc=size=>{abort()};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){throw FS.genericErrors[44]},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{var dep=!noRunDep?getUniqueRunDependency(`al ${url}`):"";readAsync(url,arrayBuffer=>{assert(arrayBuffer,`Loading data file "${url}" failed (no arrayBuffer).`);onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)};var FS_createDataFile=(parent,name,fileData,canRead,canWrite,canOwn)=>FS.createDataFile(parent,name,fileData,canRead,canWrite,canOwn);var preloadPlugins=Module["preloadPlugins"]||[];var FS_handledByPreloadPlugin=(byteArray,fullname,finish,onerror)=>{if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(plugin=>{if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}};var FS_modeStringToFlags=str=>{var flagModes={"r":0,"r+":2,"w":512|64|1,"w+":512|64|2,"a":1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath(path,opts={}){path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get(){return this.node},set(val){this.node=val}},isRead:{get(){return(this.flags&2097155)!==1}},isWrite:{get(){return(this.flags&2097155)!==0}},isAppend:{get(){return this.flags&1024}},flags:{get(){return this.shared.flags},set(val){this.shared.flags=val}},position:{get(){return this.shared.position},set(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var i=0;i0,ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error(`Invalid encoding type "${opts.encoding}"`)}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams(){if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError(){if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.name="ErrnoError";this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit(){FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init(input,output,error){FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit(){FS.init.initialized=false;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAP32[buf+12>>2]=stat.uid;HEAP32[buf+16>>2]=stat.gid;HEAP32[buf+20>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+24>>2]=tempI64[0],HEAP32[buf+28>>2]=tempI64[1];HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAPU32[buf+48>>2]=atime%1e3*1e3;tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=mtime%1e3*1e3;tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=ctime%1e3*1e3;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];return 0},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get(){var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret},getp(){return SYSCALLS.get()},getStr(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.getp();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=SYSCALLS.getp();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17>>0]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=SYSCALLS.getp();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17>>0])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag:c_iflag,c_oflag:c_oflag,c_cflag:c_cflag,c_lflag:c_lflag,c_cc:c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.getp();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.getp();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=SYSCALLS.getp();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var tupleRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var InternalError;var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_array=rawTupleType=>{var reg=tupleRegistrations[rawTupleType];delete tupleRegistrations[rawTupleType];var elements=reg.elements;var elementsLength=elements.length;var elementTypes=elements.map(elt=>elt.getterReturnType).concat(elements.map(elt=>elt.setterArgumentType));var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;whenDependentTypesAreResolved([rawTupleType],elementTypes,function(elementTypes){elements.forEach((elt,i)=>{var getterReturnType=elementTypes[i];var getter=elt.getter;var getterContext=elt.getterContext;var setterArgumentType=elementTypes[i+elementsLength];var setter=elt.setter;var setterContext=elt.setterContext;elt.read=ptr=>getterReturnType["fromWireType"](getter(getterContext,ptr));elt.write=(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}});return[{name:reg.name,"fromWireType":ptr=>{var rv=new Array(elementsLength);for(var i=0;i{if(elementsLength!==o.length){throw new TypeError(`Incorrect number of tuple elements for ${reg.name}: expected=${elementsLength}, actual=${o.length}`)}var ptr=rawConstructor();for(var i=0;i{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>getterReturnType["fromWireType"](getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{};var embind_init_charCodes=()=>{var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes};var embind_charCodes;var readLatin1String=ptr=>{var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret};var BindingError;var throwBindingError=message=>{throw new BindingError(message)};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}return sharedRegisterType(rawType,registeredInstance,options)}var GenericWireTypeSize=8;var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":function(pointer){return this["fromWireType"](HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var finalizationRegistry=false;var detachFinalizer=handle=>{};var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var registeredPointers={};var getInheritedInstanceCount=()=>Object.keys(registeredInstances).length;var getLiveInheritedInstances=()=>{var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var setDelayFunction=fn=>{delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}};var init_embind=()=>{Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var attachFinalizer=handle=>{if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var init_ClassHandle=()=>{Object.assign(ClassHandle.prototype,{"isAliasOf"(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},"clone"(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},"delete"(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},"isDeleted"(){return!this.$$.ptr},"deleteLater"(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}})};function ClassHandle(){}var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function createNamedFunction(name,body){name=makeLegalFunctionName(name);return{[name]:function(){return body.apply(this,arguments)}}[name]}var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${arguments.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function readPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":readPointer,"deleteObject"(handle){if(handle!==null){handle["delete"]()}},"fromWireType":RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var dynCallLegacy=(sig,ptr,args)=>{var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)};var wasmTableMirror=[];var wasmTable;var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var dynCall=(sig,ptr,args)=>{if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn};var getDynCaller=(sig,ptr)=>{var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}};var embind__requireFunction=(signature,rawFunction)=>{signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};var extendError=(baseErrorType,errorName)=>{var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return`${this.name}: ${this.message}`}};return errorClass};var UnboundTypeError;var getTypeName=type=>{var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv};var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);if(upcast){upcast=embind__requireFunction(upcastSignature,upcast)}if(downcast){downcast=embind__requireFunction(downcastSignature,downcast)}rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],function(base){base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(legalFunctionName,function(){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[arguments.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${arguments.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,arguments)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){if(registeredClass.baseClass.__derivedClasses===undefined){registeredClass.baseClass.__derivedClasses=[]}registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function newFunc(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError(`new_ called with constructor type ${typeof constructor} which is not a function`)}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc,isAsync){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns||isAsync?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i{var array=[];for(var i=0;i>2])}return array};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=readLatin1String(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],function(classType){classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],function(types){var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType["fromWireType"](getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};function handleAllocatorInit(){Object.assign(HandleAllocator.prototype,{get(id){return this.allocated[id]},has(id){return this.allocated[id]!==undefined},allocate(handle){var id=this.freelist.pop()||this.allocated.length;this.allocated[id]=handle;return id},free(id){this.allocated[id]=undefined;this.freelist.push(id)}})}function HandleAllocator(){this.allocated=[undefined];this.freelist=[]}var emval_handles=new HandleAllocator;var __emval_decref=handle=>{if(handle>=emval_handles.reserved&&0===--emval_handles.get(handle).refcount){emval_handles.free(handle)}};var count_emval_handles=()=>{var count=0;for(var i=emval_handles.reserved;i{emval_handles.allocated.push({value:undefined},{value:null},{value:true},{value:false});emval_handles.reserved=emval_handles.allocated.length;Module["count_emval_handles"]=count_emval_handles};var Emval={toValue:handle=>{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handles.get(handle).value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{return emval_handles.allocate({refcount:1,value:value})}}}};var __embind_register_emval=(rawType,name)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":(destructors,value)=>Emval.toHandle(value),"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})};var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this["fromWireType"](HEAP8[pointer>>0])}:function(pointer){return this["fromWireType"](HEAPU8[pointer>>0])};case 2:return signed?function(pointer){return this["fromWireType"](HEAP16[pointer>>1])}:function(pointer){return this["fromWireType"](HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this["fromWireType"](HEAP32[pointer>>2])}:function(pointer){return this["fromWireType"](HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_enum=(rawType,name,size,isSigned)=>{name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":(destructors,c)=>c.value,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 8:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":value=>value,"toWireType":(destructors,value)=>value,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer>>0]:pointer=>HEAPU8[pointer>>0];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var __embind_register_std_string=(rawType,name)=>{name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType"(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;i{var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder)return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str};var stringToUTF16=(str,outPtr,maxBytesToWrite)=>{if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead)=>{var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=readLatin1String(name);var decodeString,encodeString,getHeap,lengthBytesUTF,shift;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;getHeap=()=>HEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":value=>{var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_array=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}};var __embind_register_value_array_element=(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})};var __embind_register_void=(rawType,name)=>{name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":()=>undefined,"toWireType":(destructors,o)=>undefined})};var nowIsMonotonic=true;var __emscripten_get_now_is_monotonic=()=>nowIsMonotonic;var __emval_as=(handle,returnType,destructorsRef)=>{handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=Emval.toHandle(destructors);HEAPU32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)};var __emval_get_property=(handle,key)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);return Emval.toHandle(handle[key])};var __emval_incref=handle=>{if(handle>4){emval_handles.get(handle).refcount+=1}};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol};var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_take_value=(type,arg)=>{type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)};var _abort=()=>{abort("")};var readEmAsmArgsArray=[];var readEmAsmArgs=(sigPtr,buf)=>{readEmAsmArgsArray.length=0;var ch;while(ch=HEAPU8[sigPtr++]){var wide=ch!=105;wide&=ch!=112;buf+=wide&&buf%8?4:0;readEmAsmArgsArray.push(ch==112?HEAPU32[buf>>2]:ch==105?HEAP32[buf>>2]:HEAPF64[buf>>3]);buf+=wide?8:4}return readEmAsmArgsArray};var runEmAsmFunction=(code,sigPtr,argbuf)=>{var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)};var _emscripten_asm_const_int=(code,sigPtr,argbuf)=>runEmAsmFunction(code,sigPtr,argbuf);var _emscripten_date_now=()=>Date.now();var _emscripten_err=str=>err(UTF8ToString(str));var getHeapMax=()=>2147483648;var _emscripten_get_heap_max=()=>getHeapMax();var _emscripten_get_now;_emscripten_get_now=()=>performance.now();var _emscripten_memcpy_js=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);var _emscripten_out=str=>out(UTF8ToString(str));var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var stringToAscii=(str,buffer)=>{for(var i=0;i>0]=str.charCodeAt(i)}HEAP8[buffer>>0]=0};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;getEnvStrings().forEach((string,i)=>{var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(string=>bufSize+=string.length+1);HEAPU32[penviron_buf_size>>2]=bufSize;return 0};function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!=="undefined"){offset+=curr}}return ret};function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var _getentropy=(buffer,size)=>{randomFill(HEAPU8.subarray(buffer,buffer+size));return 0};var webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"));var webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"));var webgl_enable_WEBGL_multi_draw=ctx=>!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"));var GL={counter:1,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],byteSizeByTypeRoot:5120,byteSizeByType:[1,1,2,2,4,4,4,2,3,4,8],stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:table=>{var ret=GL.counter++;for(var i=table.length;i32-Math.clz32(i===0?0:i-1),generateTempBuffers:(quads,context)=>{var largestIndex=GL.log2ceilLookup(GL.MAX_TEMP_BUFFER_SIZE);context.tempVertexBufferCounters1=[];context.tempVertexBufferCounters2=[];context.tempVertexBufferCounters1.length=context.tempVertexBufferCounters2.length=largestIndex+1;context.tempVertexBuffers1=[];context.tempVertexBuffers2=[];context.tempVertexBuffers1.length=context.tempVertexBuffers2.length=largestIndex+1;context.tempIndexBuffers=[];context.tempIndexBuffers.length=largestIndex+1;for(var i=0;i<=largestIndex;++i){context.tempIndexBuffers[i]=null;context.tempVertexBufferCounters1[i]=context.tempVertexBufferCounters2[i]=0;var ringbufferLength=GL.numTempVertexBuffersPerSize;context.tempVertexBuffers1[i]=[];context.tempVertexBuffers2[i]=[];var ringbuffer1=context.tempVertexBuffers1[i];var ringbuffer2=context.tempVertexBuffers2[i];ringbuffer1.length=ringbuffer2.length=ringbufferLength;for(var j=0;j>1;var quadIndexes=new Uint16Array(numIndexes);var i=0,v=0;while(1){quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+1;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v;if(i>=numIndexes)break;quadIndexes[i++]=v+2;if(i>=numIndexes)break;quadIndexes[i++]=v+3;if(i>=numIndexes)break;v+=4}context.GLctx.bufferData(34963,quadIndexes,35044);context.GLctx.bindBuffer(34963,null)}},getTempVertexBuffer:function getTempVertexBuffer(sizeBytes){var idx=GL.log2ceilLookup(sizeBytes);var ringbuffer=GL.currentContext.tempVertexBuffers1[idx];var nextFreeBufferIndex=GL.currentContext.tempVertexBufferCounters1[idx];GL.currentContext.tempVertexBufferCounters1[idx]=GL.currentContext.tempVertexBufferCounters1[idx]+1&GL.numTempVertexBuffersPerSize-1;var vbo=ringbuffer[nextFreeBufferIndex];if(vbo){return vbo}var prevVBO=GLctx.getParameter(34964);ringbuffer[nextFreeBufferIndex]=GLctx.createBuffer();GLctx.bindBuffer(34962,ringbuffer[nextFreeBufferIndex]);GLctx.bufferData(34962,1<{var source="";for(var i=0;i>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},calcBufLength:function calcBufLength(size,type,stride,count){if(stride>0){return count*stride}var typeSize=GL.byteSizeByType[type-GL.byteSizeByTypeRoot];return size*typeSize*count},usedTempBuffers:[],preDrawHandleClientVertexAttribBindings:function preDrawHandleClientVertexAttribBindings(count){GL.resetBufferBinding=false;for(var i=0;i{if(!canvas.getContextSafariWebGL2Fixed){canvas.getContextSafariWebGL2Fixed=canvas.getContext;function fixedGetContext(ver,attrs){var gl=canvas.getContextSafariWebGL2Fixed(ver,attrs);return ver=="webgl"==gl instanceof WebGLRenderingContext?gl:null}canvas.getContext=fixedGetContext}var ctx=canvas.getContext("webgl2",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},registerContext:(ctx,webGLContextAttributes)=>{var handle=GL.getNewId(GL.contexts);var context={handle:handle,attributes:webGLContextAttributes,version:webGLContextAttributes.majorVersion,GLctx:ctx};if(ctx.canvas)ctx.canvas.GLctxObject=context;GL.contexts[handle]=context;if(typeof webGLContextAttributes.enableExtensionsByDefault=="undefined"||webGLContextAttributes.enableExtensionsByDefault){GL.initExtensions(context)}context.maxVertexAttribs=context.GLctx.getParameter(34921);context.clientBuffers=[];for(var i=0;i{GL.currentContext=GL.contexts[contextHandle];Module.ctx=GLctx=GL.currentContext&&GL.currentContext.GLctx;return!(contextHandle&&!GLctx)},getContext:contextHandle=>GL.contexts[contextHandle],deleteContext:contextHandle=>{if(GL.currentContext===GL.contexts[contextHandle])GL.currentContext=null;if(typeof JSEvents=="object")JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas);if(GL.contexts[contextHandle]&&GL.contexts[contextHandle].GLctx.canvas)GL.contexts[contextHandle].GLctx.canvas.GLctxObject=undefined;GL.contexts[contextHandle]=null},initExtensions:context=>{if(!context)context=GL.currentContext;if(context.initExtensionsDone)return;context.initExtensionsDone=true;var GLctx=context.GLctx;webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx);webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx);if(context.version>=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}webgl_enable_WEBGL_multi_draw(GLctx);var exts=GLctx.getSupportedExtensions()||[];exts.forEach(ext=>{if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}})}};function _glActiveTexture(x0){GLctx.activeTexture(x0)}var _glAttachShader=(program,shader)=>{GLctx.attachShader(GL.programs[program],GL.shaders[shader])};var _glBeginQuery=(target,id)=>{GLctx.beginQuery(target,GL.queries[id])};var _glBindAttribLocation=(program,index,name)=>{GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))};var _glBindBuffer=(target,buffer)=>{if(target==34962){GLctx.currentArrayBufferBinding=buffer}else if(target==34963){GLctx.currentElementArrayBufferBinding=buffer}if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])};var _glBindBufferBase=(target,index,buffer)=>{GLctx.bindBufferBase(target,index,GL.buffers[buffer])};var _glBindBufferRange=(target,index,buffer,offset,ptrsize)=>{GLctx.bindBufferRange(target,index,GL.buffers[buffer],offset,ptrsize)};var _glBindFramebuffer=(target,framebuffer)=>{GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])};var _glBindRenderbuffer=(target,renderbuffer)=>{GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])};var _glBindSampler=(unit,sampler)=>{GLctx.bindSampler(unit,GL.samplers[sampler])};var _glBindTexture=(target,texture)=>{GLctx.bindTexture(target,GL.textures[texture])};var _glBindVertexArray=vao=>{GLctx.bindVertexArray(GL.vaos[vao]);var ibo=GLctx.getParameter(34965);GLctx.currentElementArrayBufferBinding=ibo?ibo.name|0:0};function _glBlendEquationSeparate(x0,x1){GLctx.blendEquationSeparate(x0,x1)}function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx.blendFuncSeparate(x0,x1,x2,x3)}function _glBlitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9){GLctx.blitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)}var _glBufferData=(target,size,data,usage)=>{if(true){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}}else{GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}};var _glBufferSubData=(target,offset,size,data)=>{if(true){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))};function _glClear(x0){GLctx.clear(x0)}function _glClearBufferfi(x0,x1,x2,x3){GLctx.clearBufferfi(x0,x1,x2,x3)}var _glClearBufferfv=(buffer,drawbuffer,value)=>{GLctx.clearBufferfv(buffer,drawbuffer,HEAPF32,value>>2)};var _glClearBufferiv=(buffer,drawbuffer,value)=>{GLctx.clearBufferiv(buffer,drawbuffer,HEAP32,value>>2)};function _glClearColor(x0,x1,x2,x3){GLctx.clearColor(x0,x1,x2,x3)}function _glClearDepthf(x0){GLctx.clearDepth(x0)}function _glClearStencil(x0){GLctx.clearStencil(x0)}var convertI32PairToI53=(lo,hi)=>(lo>>>0)+hi*4294967296;var _glClientWaitSync=(sync,flags,timeout_low,timeout_high)=>{var timeout=convertI32PairToI53(timeout_low,timeout_high);return GLctx.clientWaitSync(GL.syncs[sync],flags,timeout)};var _glColorMask=(red,green,blue,alpha)=>{GLctx.colorMask(!!red,!!green,!!blue,!!alpha)};var _glCompileShader=shader=>{GLctx.compileShader(GL.shaders[shader])};var _glCompressedTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,imageSize,data)=>{if(true){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data)}else{GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,HEAPU8,data,imageSize)}return}GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,data?HEAPU8.subarray(data,data+imageSize):null)};var _glCompressedTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}};function _glCopyBufferSubData(x0,x1,x2,x3,x4){GLctx.copyBufferSubData(x0,x1,x2,x3,x4)}var _glCreateProgram=()=>{var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id};var _glCreateShader=shaderType=>{var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id};function _glCullFace(x0){GLctx.cullFace(x0)}var _glDeleteBuffers=(n,buffers)=>{for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentArrayBufferBinding)GLctx.currentArrayBufferBinding=0;if(id==GLctx.currentElementArrayBufferBinding)GLctx.currentElementArrayBufferBinding=0;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}};var _glDeleteFramebuffers=(n,framebuffers)=>{for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}};var _glDeleteProgram=id=>{if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null};var _glDeleteQueries=(n,ids)=>{for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.deleteQuery(query);GL.queries[id]=null}};var _glDeleteRenderbuffers=(n,renderbuffers)=>{for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}};var _glDeleteSamplers=(n,samplers)=>{for(var i=0;i>2];var sampler=GL.samplers[id];if(!sampler)continue;GLctx.deleteSampler(sampler);sampler.name=0;GL.samplers[id]=null}};var _glDeleteShader=id=>{if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null};var _glDeleteSync=id=>{if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null};var _glDeleteTextures=(n,textures)=>{for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}};var _glDeleteVertexArrays=(n,vaos)=>{for(var i=0;i>2];GLctx.deleteVertexArray(GL.vaos[id]);GL.vaos[id]=null}};function _glDepthFunc(x0){GLctx.depthFunc(x0)}var _glDepthMask=flag=>{GLctx.depthMask(!!flag)};function _glDepthRangef(x0,x1){GLctx.depthRange(x0,x1)}var _glDetachShader=(program,shader)=>{GLctx.detachShader(GL.programs[program],GL.shaders[shader])};function _glDisable(x0){GLctx.disable(x0)}var _glDisableVertexAttribArray=index=>{var cb=GL.currentContext.clientBuffers[index];cb.enabled=false;GLctx.disableVertexAttribArray(index)};var tempFixedLengthArray=[];var _glDrawBuffers=(n,bufs)=>{var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx.drawBuffers(bufArray)};var _glDrawElements=(mode,count,type,indices)=>{var buf;if(!GLctx.currentElementArrayBufferBinding){var size=GL.calcBufLength(1,type,0,count);buf=GL.getTempIndexBuffer(size);GLctx.bindBuffer(34963,buf);GLctx.bufferSubData(34963,0,HEAPU8.subarray(indices,indices+size));indices=0}GL.preDrawHandleClientVertexAttribBindings(count);GLctx.drawElements(mode,count,type,indices);GL.postDrawHandleClientVertexAttribBindings(count);if(!GLctx.currentElementArrayBufferBinding){GLctx.bindBuffer(34963,null)}};var _glDrawElementsInstanced=(mode,count,type,indices,primcount)=>{GLctx.drawElementsInstanced(mode,count,type,indices,primcount)};function _glEnable(x0){GLctx.enable(x0)}var _glEnableVertexAttribArray=index=>{var cb=GL.currentContext.clientBuffers[index];cb.enabled=true;GLctx.enableVertexAttribArray(index)};function _glEndQuery(x0){GLctx.endQuery(x0)}var _glFenceSync=(condition,flags)=>{var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}return 0};function _glFinish(){GLctx.finish()}function _glFlush(){GLctx.flush()}var _glFramebufferRenderbuffer=(target,attachment,renderbuffertarget,renderbuffer)=>{GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])};var _glFramebufferTexture2D=(target,attachment,textarget,texture,level)=>{GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)};var _glFramebufferTextureLayer=(target,attachment,texture,level,layer)=>{GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)};function _glFrontFace(x0){GLctx.frontFace(x0)}var __glGenObject=(n,buffers,createFunction,objectTable)=>{for(var i=0;i>2]=id}};var _glGenBuffers=(n,buffers)=>{__glGenObject(n,buffers,"createBuffer",GL.buffers)};var _glGenFramebuffers=(n,ids)=>{__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)};var _glGenQueries=(n,ids)=>{__glGenObject(n,ids,"createQuery",GL.queries)};var _glGenRenderbuffers=(n,renderbuffers)=>{__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)};var _glGenSamplers=(n,samplers)=>{__glGenObject(n,samplers,"createSampler",GL.samplers)};var _glGenTextures=(n,textures)=>{__glGenObject(n,textures,"createTexture",GL.textures)};function _glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}function _glGenerateMipmap(x0){GLctx.generateMipmap(x0)}var _glGetBufferSubData=(target,offset,size,data)=>{if(!data){GL.recordError(1281);return}size&&GLctx.getBufferSubData(target,offset,HEAPU8,data,size)};var _glGetError=()=>{var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error};var writeI53ToI64=(ptr,num)=>{HEAPU32[ptr>>2]=num;var lower=HEAPU32[ptr>>2];HEAPU32[ptr+4>>2]=(num-lower)/4294967296};var emscriptenWebGLGet=(name_,p,type)=>{if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}var exts=GLctx.getSupportedExtensions()||[];ret=2*exts.length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}};var _glGetFloatv=(name_,p)=>{emscriptenWebGLGet(name_,p,2)};var _glGetIntegerv=(name_,p)=>{emscriptenWebGLGet(name_,p,0)};var _glGetProgramBinary=(program,bufSize,length,binaryFormat,binary)=>{GL.recordError(1282)};var _glGetProgramInfoLog=(program,maxLength,length,infoLog)=>{var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _glGetProgramiv=(program,pname,p)=>{if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}};var _glGetQueryObjectuiv=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.getQueryParameter(query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret};var _glGetShaderInfoLog=(shader,maxLength,length,infoLog)=>{var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _glGetShaderiv=(shader,pname,p)=>{if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}};var stringToNewUTF8=str=>{var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8(str,ret,size);return ret};var _glGetString=name_=>{var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(e=>"GL_"+e));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);if(true)glVersion="OpenGL ES 3.0 ("+glVersion+")";else{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret};var _glGetUniformBlockIndex=(program,uniformBlockName)=>GLctx.getUniformBlockIndex(GL.programs[program],UTF8ToString(uniformBlockName));var jstoi_q=str=>parseInt(str);var webglGetLeftBracePos=name=>name.slice(-1)=="]"&&name.lastIndexOf("[");var webglPrepareUniformLocationsBeforeFirstUse=program=>{var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j{name=UTF8ToString(name);if(program=GL.programs[program]){webglPrepareUniformLocationsBeforeFirstUse(program);var uniformLocsById=program.uniformLocsById;var arrayIndex=0;var uniformBaseName=name;var leftBrace=webglGetLeftBracePos(name);if(leftBrace>0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex{var list=tempFixedLengthArray[numAttachments];for(var i=0;i>2]}GLctx.invalidateFramebuffer(target,list)};var _glLinkProgram=program=>{program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}};var emscriptenWebGLGetBufferBinding=target=>{switch(target){case 34962:target=34964;break;case 34963:target=34965;break;case 35051:target=35053;break;case 35052:target=35055;break;case 35982:target=35983;break;case 36662:target=36662;break;case 36663:target=36663;break;case 35345:target=35368;break}var buffer=GLctx.getParameter(target);if(buffer)return buffer.name|0;else return 0};var emscriptenWebGLValidateMapBufferTarget=target=>{switch(target){case 34962:case 34963:case 36662:case 36663:case 35051:case 35052:case 35882:case 35982:case 35345:return true;default:return false}};var _glMapBufferRange=(target,offset,length,access)=>{if((access&(1|32))!=0){err("glMapBufferRange access does not support MAP_READ or MAP_UNSYNCHRONIZED");return 0}if((access&2)==0){err("glMapBufferRange access must include MAP_WRITE");return 0}if((access&(4|8))==0){err("glMapBufferRange access must include INVALIDATE_BUFFER or INVALIDATE_RANGE");return 0}if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glMapBufferRange");return 0}var mem=_malloc(length),binding=emscriptenWebGLGetBufferBinding(target);if(!mem)return 0;if(!GL.mappedBuffers[binding])GL.mappedBuffers[binding]={};binding=GL.mappedBuffers[binding];binding.offset=offset;binding.length=length;binding.mem=mem;binding.access=access;return mem};var _glPixelStorei=(pname,param)=>{if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)};function _glPolygonOffset(x0,x1){GLctx.polygonOffset(x0,x1)}var _glProgramBinary=(program,binaryFormat,binary,length)=>{GL.recordError(1280)};var computeUnpackAlignedImageSize=(width,height,sizePerPixel,alignment)=>{function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=width*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,alignment);return height*alignedRowSize};var colorChannelsInGlTextureFormat=format=>{var colorChannels={5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4};return colorChannels[format-6402]||1};var heapObjectForWebGLType=type=>{type-=5120;if(type==0)return HEAP8;if(type==1)return HEAPU8;if(type==2)return HEAP16;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922||type==28520||type==30779||type==30782)return HEAPU32;return HEAPU16};var heapAccessShiftForWebGLHeap=heap=>31-Math.clz32(heap.BYTES_PER_ELEMENT);var emscriptenWebGLGetTexPixelData=(type,format,width,height,pixels,internalFormat)=>{var heap=heapObjectForWebGLType(type);var shift=heapAccessShiftForWebGLHeap(heap);var byteSize=1<>shift,pixels+bytes>>shift)};var _glReadPixels=(x,y,width,height,format,type,pixels)=>{if(true){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels)}else{var heap=heapObjectForWebGLType(type);GLctx.readPixels(x,y,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)};function _glRenderbufferStorage(x0,x1,x2,x3){GLctx.renderbufferStorage(x0,x1,x2,x3)}function _glRenderbufferStorageMultisample(x0,x1,x2,x3,x4){GLctx.renderbufferStorageMultisample(x0,x1,x2,x3,x4)}var _glSamplerParameterf=(sampler,pname,param)=>{GLctx.samplerParameterf(GL.samplers[sampler],pname,param)};var _glSamplerParameteri=(sampler,pname,param)=>{GLctx.samplerParameteri(GL.samplers[sampler],pname,param)};function _glScissor(x0,x1,x2,x3){GLctx.scissor(x0,x1,x2,x3)}var _glShaderSource=(shader,count,string,length)=>{var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)};function _glStencilFuncSeparate(x0,x1,x2,x3){GLctx.stencilFuncSeparate(x0,x1,x2,x3)}function _glStencilMaskSeparate(x0,x1){GLctx.stencilMaskSeparate(x0,x1)}function _glStencilOpSeparate(x0,x1,x2,x3){GLctx.stencilOpSeparate(x0,x1,x2,x3)}var _glTexImage2D=(target,level,internalFormat,width,height,border,format,type,pixels)=>{if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,null)}return}GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)};function _glTexParameterf(x0,x1,x2){GLctx.texParameterf(x0,x1,x2)}function _glTexParameteri(x0,x1,x2){GLctx.texParameteri(x0,x1,x2)}function _glTexStorage2D(x0,x1,x2,x3,x4){GLctx.texStorage2D(x0,x1,x2,x3,x4)}function _glTexStorage3D(x0,x1,x2,x3,x4,x5){GLctx.texStorage3D(x0,x1,x2,x3,x4,x5)}var _glTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,type,pixels)=>{if(true){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,null)}return}var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)};var _glTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}};var webglGetUniformLocation=location=>{var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?"["+webglLoc+"]":""))}return webglLoc}else{GL.recordError(1282)}};var _glUniform1fv=(location,count,value)=>{count&&GLctx.uniform1fv(webglGetUniformLocation(location),HEAPF32,value>>2,count)};var _glUniform1i=(location,v0)=>{GLctx.uniform1i(webglGetUniformLocation(location),v0)};var _glUniform1iv=(location,count,value)=>{count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count)};var _glUniform2fv=(location,count,value)=>{count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2)};var _glUniform2iv=(location,count,value)=>{count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2)};var _glUniform3fv=(location,count,value)=>{count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3)};var _glUniform3iv=(location,count,value)=>{count&&GLctx.uniform3iv(webglGetUniformLocation(location),HEAP32,value>>2,count*3)};var _glUniform4fv=(location,count,value)=>{count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4)};var _glUniform4iv=(location,count,value)=>{count&&GLctx.uniform4iv(webglGetUniformLocation(location),HEAP32,value>>2,count*4)};var _glUniformBlockBinding=(program,uniformBlockIndex,uniformBlockBinding)=>{program=GL.programs[program];GLctx.uniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding)};var _glUniformMatrix3fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9)};var _glUniformMatrix4fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16)};var _glUnmapBuffer=target=>{if(!emscriptenWebGLValidateMapBufferTarget(target)){GL.recordError(1280);err("GL_INVALID_ENUM in glUnmapBuffer");return 0}var buffer=emscriptenWebGLGetBufferBinding(target);var mapping=GL.mappedBuffers[buffer];if(!mapping||!mapping.mem){GL.recordError(1282);err("buffer was never mapped in glUnmapBuffer");return 0}if(!(mapping.access&16))if(true){GLctx.bufferSubData(target,mapping.offset,HEAPU8,mapping.mem,mapping.length)}else{GLctx.bufferSubData(target,mapping.offset,HEAPU8.subarray(mapping.mem,mapping.mem+mapping.length))}_free(mapping.mem);mapping.mem=0;return 1};var _glUseProgram=program=>{program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program};function _glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx.vertexAttrib4f(x0,x1,x2,x3,x4)}function _glVertexAttribI4ui(x0,x1,x2,x3,x4){GLctx.vertexAttribI4ui(x0,x1,x2,x3,x4)}var _glVertexAttribIPointer=(index,size,type,stride,ptr)=>{var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=false;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribIPointer(index,size,type,stride,ptr)};return}cb.clientside=false;GLctx.vertexAttribIPointer(index,size,type,stride,ptr)};var _glVertexAttribPointer=(index,size,type,normalized,stride,ptr)=>{var cb=GL.currentContext.clientBuffers[index];if(!GLctx.currentArrayBufferBinding){cb.size=size;cb.type=type;cb.normalized=normalized;cb.stride=stride;cb.ptr=ptr;cb.clientside=true;cb.vertexAttribPointerAdaptor=function(index,size,type,normalized,stride,ptr){this.vertexAttribPointer(index,size,type,normalized,stride,ptr)};return}cb.clientside=false;GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)};function _glViewport(x0,x1,x2,x3){GLctx.viewport(x0,x1,x2,x3)}var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var arraySum=(array,index)=>{var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum};var MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];var addDays=(date,days)=>{var newDate=new Date(date.getTime());while(days>0){var leap=isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var _strftime=(s,maxsize,format,tm)=>{var tm_zone=HEAPU32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":date=>WEEKDAYS[date.tm_wday].substring(0,3),"%A":date=>WEEKDAYS[date.tm_wday],"%b":date=>MONTHS[date.tm_mon].substring(0,3),"%B":date=>MONTHS[date.tm_mon],"%C":date=>{var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":date=>leadingNulls(date.tm_mday,2),"%e":date=>leadingSomething(date.tm_mday,2," "),"%g":date=>getWeekBasedYear(date).toString().substring(2),"%G":date=>getWeekBasedYear(date),"%H":date=>leadingNulls(date.tm_hour,2),"%I":date=>{var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":date=>leadingNulls(date.tm_mday+arraySum(isLeapYear(date.tm_year+1900)?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR,date.tm_mon-1),3),"%m":date=>leadingNulls(date.tm_mon+1,2),"%M":date=>leadingNulls(date.tm_min,2),"%n":()=>"\n","%p":date=>{if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":date=>leadingNulls(date.tm_sec,2),"%t":()=>"\t","%u":date=>date.tm_wday||7,"%U":date=>{var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":date=>{var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":date=>date.tm_wday,"%W":date=>{var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":date=>(date.tm_year+1900).toString().substring(2),"%Y":date=>date.tm_year+1900,"%z":date=>{var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":date=>date.tm_zone,"%%":()=>"%"};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1};var _strftime_l=(s,maxsize,format,tm,loc)=>_strftime(s,maxsize,format,tm);var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();InternalError=Module["InternalError"]=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};embind_init_charCodes();BindingError=Module["BindingError"]=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");handleAllocatorInit();init_emval();var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var wasmImports={Ha:___syscall_fcntl64,Ib:___syscall_ioctl,Jb:___syscall_openat,Eb:___syscall_stat64,y:__embind_finalize_value_array,m:__embind_finalize_value_object,yb:__embind_register_bigint,Pb:__embind_register_bool,e:__embind_register_class,l:__embind_register_class_class_function,o:__embind_register_class_constructor,a:__embind_register_class_function,x:__embind_register_class_property,Ob:__embind_register_emval,h:__embind_register_enum,b:__embind_register_enum_value,Ja:__embind_register_float,_:__embind_register_function,D:__embind_register_integer,p:__embind_register_memory_view,Ia:__embind_register_std_string,oa:__embind_register_std_wstring,z:__embind_register_value_array,f:__embind_register_value_array_element,n:__embind_register_value_object,d:__embind_register_value_object_field,Qb:__embind_register_void,Lb:__emscripten_get_now_is_monotonic,t:__emval_as,g:__emval_decref,u:__emval_get_property,Z:__emval_incref,H:__emval_new_cstring,s:__emval_run_destructors,v:__emval_take_value,c:_abort,ma:_emscripten_asm_const_int,Mb:_emscripten_date_now,wb:_emscripten_err,Db:_emscripten_get_heap_max,Kb:_emscripten_get_now,Nb:_emscripten_memcpy_js,Fa:_emscripten_out,Cb:_emscripten_resize_heap,Fb:_environ_get,Gb:_environ_sizes_get,na:_fd_close,Hb:_fd_read,xb:_fd_seek,Ga:_fd_write,Ab:_getentropy,i:_glActiveTexture,la:_glAttachShader,ib:_glBeginQuery,qb:_glBindAttribLocation,q:_glBindBuffer,ua:_glBindBufferBase,pa:_glBindBufferRange,k:_glBindFramebuffer,Oa:_glBindRenderbuffer,ja:_glBindSampler,j:_glBindTexture,eb:_glBindVertexArray,xa:_glBlendEquationSeparate,wa:_glBlendFuncSeparate,$:_glBlitFramebuffer,F:_glBufferData,aa:_glBufferSubData,Ub:_glClear,Zb:_glClearBufferfi,E:_glClearBufferfv,Yb:_glClearBufferiv,Xb:_glClearColor,Wb:_glClearDepthf,Vb:_glClearStencil,zb:_glClientWaitSync,ga:_glColorMask,rb:_glCompileShader,La:_glCompressedTexSubImage2D,Ka:_glCompressedTexSubImage3D,jc:_glCopyBufferSubData,Ea:_glCreateProgram,tb:_glCreateShader,ya:_glCullFace,ia:_glDeleteBuffers,S:_glDeleteFramebuffers,X:_glDeleteProgram,hb:_glDeleteQueries,Sa:_glDeleteRenderbuffers,Aa:_glDeleteSamplers,P:_glDeleteShader,Qa:_glDeleteSync,Ta:_glDeleteTextures,fb:_glDeleteVertexArrays,ka:_glDepthFunc,fa:_glDepthMask,ra:_glDepthRangef,Q:_glDetachShader,r:_glDisable,ac:_glDisableVertexAttribArray,mc:_glDrawBuffers,ic:_glDrawElements,hc:_glDrawElementsInstanced,A:_glEnable,dc:_glEnableVertexAttribArray,jb:_glEndQuery,ba:_glFenceSync,Da:_glFinish,mb:_glFlush,I:_glFramebufferRenderbuffer,C:_glFramebufferTexture2D,M:_glFramebufferTextureLayer,za:_glFrontFace,U:_glGenBuffers,ca:_glGenFramebuffers,gb:_glGenQueries,ea:_glGenRenderbuffers,Ca:_glGenSamplers,T:_glGenTextures,db:_glGenVertexArrays,kc:_glGenerateMipmap,Tb:_glGetBufferSubData,Y:_glGetError,cb:_glGetFloatv,B:_glGetIntegerv,ub:_glGetProgramBinary,nb:_glGetProgramInfoLog,R:_glGetProgramiv,kb:_glGetQueryObjectuiv,ob:_glGetShaderInfoLog,O:_glGetShaderiv,J:_glGetString,ab:_glGetUniformBlockIndex,da:_glGetUniformLocation,bb:_glHint,lb:_glInvalidateFramebuffer,pb:_glLinkProgram,Sb:_glMapBufferRange,N:_glPixelStorei,va:_glPolygonOffset,vb:_glProgramBinary,Ra:_glReadPixels,_b:_glRenderbufferStorage,$b:_glRenderbufferStorageMultisample,Ba:_glSamplerParameterf,K:_glSamplerParameteri,ta:_glScissor,sb:_glShaderSource,W:_glStencilFuncSeparate,G:_glStencilMaskSeparate,V:_glStencilOpSeparate,L:_glTexImage2D,lc:_glTexParameterf,w:_glTexParameteri,gc:_glTexStorage2D,Pa:_glTexStorage3D,Na:_glTexSubImage2D,Ma:_glTexSubImage3D,_a:_glUniform1fv,qa:_glUniform1i,Wa:_glUniform1iv,Za:_glUniform2fv,Va:_glUniform2iv,Ya:_glUniform3fv,Ua:_glUniform3iv,Xa:_glUniform4fv,pc:_glUniform4iv,$a:_glUniformBlockBinding,oc:_glUniformMatrix3fv,nc:_glUniformMatrix4fv,Rb:_glUnmapBuffer,ha:_glUseProgram,bc:_glVertexAttrib4f,cc:_glVertexAttribI4ui,fc:_glVertexAttribIPointer,ec:_glVertexAttribPointer,sa:_glViewport,Bb:_strftime_l};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["rc"])();var _malloc=a0=>(_malloc=wasmExports["tc"])(a0);var _free=a0=>(_free=wasmExports["uc"])(a0);var ___errno_location=()=>(___errno_location=wasmExports["vc"])();var ___getTypeName=a0=>(___getTypeName=wasmExports["wc"])(a0);var __embind_initialize_bindings=Module["__embind_initialize_bindings"]=()=>(__embind_initialize_bindings=Module["__embind_initialize_bindings"]=wasmExports["xc"])();var dynCall_iiiiij=Module["dynCall_iiiiij"]=(a0,a1,a2,a3,a4,a5,a6)=>(dynCall_iiiiij=Module["dynCall_iiiiij"]=wasmExports["yc"])(a0,a1,a2,a3,a4,a5,a6);var dynCall_jii=Module["dynCall_jii"]=(a0,a1,a2)=>(dynCall_jii=Module["dynCall_jii"]=wasmExports["zc"])(a0,a1,a2);var dynCall_iiij=Module["dynCall_iiij"]=(a0,a1,a2,a3,a4)=>(dynCall_iiij=Module["dynCall_iiij"]=wasmExports["Ac"])(a0,a1,a2,a3,a4);var dynCall_iiiij=Module["dynCall_iiiij"]=(a0,a1,a2,a3,a4,a5)=>(dynCall_iiiij=Module["dynCall_iiiij"]=wasmExports["Bc"])(a0,a1,a2,a3,a4,a5);var dynCall_vij=Module["dynCall_vij"]=(a0,a1,a2,a3)=>(dynCall_vij=Module["dynCall_vij"]=wasmExports["Cc"])(a0,a1,a2,a3);var dynCall_jiji=Module["dynCall_jiji"]=(a0,a1,a2,a3,a4)=>(dynCall_jiji=Module["dynCall_jiji"]=wasmExports["Dc"])(a0,a1,a2,a3,a4);var dynCall_viijii=Module["dynCall_viijii"]=(a0,a1,a2,a3,a4,a5,a6)=>(dynCall_viijii=Module["dynCall_viijii"]=wasmExports["Ec"])(a0,a1,a2,a3,a4,a5,a6);var dynCall_iiiiijj=Module["dynCall_iiiiijj"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8)=>(dynCall_iiiiijj=Module["dynCall_iiiiijj"]=wasmExports["Fc"])(a0,a1,a2,a3,a4,a5,a6,a7,a8);var dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9)=>(dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=wasmExports["Gc"])(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9);var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); - return Filament.ready + return moduleArg.ready } + ); })(); if (typeof exports === 'object' && typeof module === 'object') module.exports = Filament; else if (typeof define === 'function' && define['amd']) - define([], function() { return Filament; }); -else if (typeof exports === 'object') - exports["Filament"] = Filament; + define([], () => Filament); /* * Copyright (C) 2018 The Android Open Source Project * @@ -294,6 +292,13 @@ Filament.loadGeneratedExtensions = function() { filterWidth: 1.0, feedback: 0.04, enabled: false, + filterHistory: true, + filterInput: true, + useYCoCg: false, + boxType: Filament.View$TemporalAntiAliasingOptions$BoxType.VARIANCE, + boxClipping: Filament.View$TemporalAntiAliasingOptions$BoxClipping.ACCURATE, + preventFlickering: false, + historyReprojection: true, }; return Object.assign(options, overrides); }; @@ -686,6 +691,12 @@ Filament.loadClassExtensions = function() { this._setGuardBandOptions(options); }; + /// setStereoscopicOptions ::method:: + Filament.View.prototype.setStereoscopicOptions = function(overrides) { + const options = this.setStereoscopicOptionsDefaults(overrides); + this._setStereoscopicOptions(options); + } + /// BufferObject ::core class:: /// setBuffer ::method:: diff --git a/docs/webgl/filament.wasm b/docs/webgl/filament.wasm index 7ba7e66d27f..f50374af8e9 100644 Binary files a/docs/webgl/filament.wasm and b/docs/webgl/filament.wasm differ diff --git a/docs/webgl/parquet.filamat b/docs/webgl/parquet.filamat index da133b52304..e63b3885890 100644 Binary files a/docs/webgl/parquet.filamat and b/docs/webgl/parquet.filamat differ diff --git a/docs/webgl/plastic.filamat b/docs/webgl/plastic.filamat index 018c14c11b4..28974b5d2be 100644 Binary files a/docs/webgl/plastic.filamat and b/docs/webgl/plastic.filamat differ diff --git a/docs/webgl/reference.html b/docs/webgl/reference.html index 77a9fce786c..caca8d96b91 100644 --- a/docs/webgl/reference.html +++ b/docs/webgl/reference.html @@ -587,6 +587,7 @@

      class Engine

    • overrides Dictionary with one or more of the following properties: mapSize, shadowCascades, constantBias, normalBias, shadowFar, shadowNearHint, shadowFarHint, stable, polygonOffsetConstant, polygonOffsetSlope, \
    +
  • engine.setStereoscopicOptions()
  • engine.setTemporalAntiAliasingOptions()
  • engine.setVignetteOptions()
  • @@ -1462,6 +1463,8 @@

    enum Texture$Usage

  • STENCIL_ATTACHMENT
  • UPLOADABLE
  • SAMPLEABLE
  • +
  • BLIT_SRC
  • +
  • BLIT_DST
  • SUBPASS_INPUT
  • diff --git a/docs/webgl/textured.filamat b/docs/webgl/textured.filamat index 62c93864cf8..6eed58e6bc7 100644 Binary files a/docs/webgl/textured.filamat and b/docs/webgl/textured.filamat differ diff --git a/docs/webgl/triangle.filamat b/docs/webgl/triangle.filamat index da9eca28cd4..e038988dba6 100644 Binary files a/docs/webgl/triangle.filamat and b/docs/webgl/triangle.filamat differ diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index b65e3cca9ba..b978d2da1bd 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -66,6 +66,7 @@ set(SRCS src/Froxelizer.cpp src/Frustum.cpp src/HwRenderPrimitiveFactory.cpp + src/HwVertexBufferInfoFactory.cpp src/IndexBuffer.cpp src/IndirectLight.cpp src/InstanceBuffer.cpp @@ -137,6 +138,7 @@ set(SRCS set(PRIVATE_HDRS src/Allocators.h + src/Bimap.h src/BufferPoolAllocator.h src/ColorSpaceUtils.h src/Culler.h @@ -147,6 +149,7 @@ set(PRIVATE_HDRS src/FrameSkipper.h src/Froxelizer.h src/HwRenderPrimitiveFactory.h + src/HwVertexBufferInfoFactory.h src/Intersections.h src/MaterialParser.h src/PerViewUniforms.h @@ -209,10 +212,14 @@ set(PRIVATE_HDRS ) set(MATERIAL_SRCS - src/materials/fsr/fsr_easu.mat - src/materials/fsr/fsr_easu_mobile.mat - src/materials/fsr/fsr_easu_mobileF.mat - src/materials/fsr/fsr_rcas.mat + src/materials/antiAliasing/fxaa.mat + src/materials/antiAliasing/taa.mat + src/materials/blitLow.mat + src/materials/blitArray.mat + src/materials/bloom/bloomDownsample.mat + src/materials/bloom/bloomDownsample2x.mat + src/materials/bloom/bloomDownsample9.mat + src/materials/bloom/bloomUpsample.mat src/materials/colorGrading/colorGrading.mat src/materials/colorGrading/colorGradingAsSubpass.mat src/materials/colorGrading/customResolveAsSubpass.mat @@ -220,34 +227,38 @@ set(MATERIAL_SRCS src/materials/defaultMaterial.mat src/materials/dof/dof.mat src/materials/dof/dofCoc.mat - src/materials/dof/dofDownsample.mat src/materials/dof/dofCombine.mat - src/materials/dof/dofTiles.mat - src/materials/dof/dofTilesSwizzle.mat src/materials/dof/dofDilate.mat - src/materials/dof/dofMipmap.mat + src/materials/dof/dofDownsample.mat src/materials/dof/dofMedian.mat + src/materials/dof/dofMipmap.mat + src/materials/dof/dofTiles.mat + src/materials/dof/dofTilesSwizzle.mat src/materials/flare/flare.mat - src/materials/blitLow.mat - src/materials/bloom/bloomDownsample.mat - src/materials/bloom/bloomDownsample2x.mat - src/materials/bloom/bloomDownsample9.mat - src/materials/bloom/bloomUpsample.mat + src/materials/fsr/fsr_easu.mat + src/materials/fsr/fsr_easu_mobile.mat + src/materials/fsr/fsr_easu_mobileF.mat + src/materials/fsr/fsr_rcas.mat + src/materials/resolveDepth.mat + src/materials/separableGaussianBlur.mat + src/materials/skybox.mat + src/materials/shadowmap.mat src/materials/ssao/bilateralBlur.mat src/materials/ssao/bilateralBlurBentNormals.mat src/materials/ssao/mipmapDepth.mat - src/materials/skybox.mat src/materials/ssao/sao.mat src/materials/ssao/saoBentNormals.mat - src/materials/separableGaussianBlur.mat - src/materials/antiAliasing/fxaa.mat - src/materials/antiAliasing/taa.mat src/materials/vsmMipmap.mat ) -set(MATERIAL_ES2_SRCS - src/materials/defaultMaterial0.mat - src/materials/skybox0.mat +set(MATERIAL_FL0_SRCS + src/materials/defaultMaterial.mat + src/materials/skybox.mat +) + +set(MATERIAL_MULTIVIEW_SRCS + src/materials/defaultMaterial.mat + src/materials/skybox.mat ) # Embed the binary resource blob for materials. @@ -274,6 +285,16 @@ if (NOT DFG_LUT_SIZE) endif() message(STATUS "DFG LUT size set to ${DFG_LUT_SIZE}x${DFG_LUT_SIZE}") +# Whether to include FL0 materials. +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 # ================================================================================================== @@ -304,33 +325,42 @@ 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 ${mat_src} to ${output_path}" + COMMENT "Compiling material ${fullname} to ${output_path}" ) list(APPEND MATERIAL_BINS ${output_path}) -endforeach() -if (IS_MOBILE_TARGET AND FILAMENT_SUPPORTS_OPENGL) - foreach (mat_src ${MATERIAL_ES2_SRCS}) - get_filename_component(localname "${mat_src}" NAME_WE) - get_filename_component(fullname "${mat_src}" ABSOLUTE) - set(output_path "${MATERIAL_DIR}/${localname}.filamat") + list(FIND MATERIAL_FL0_SRCS ${mat_src} index) + if (${index} GREATER -1 AND FILAMENT_ENABLE_FEATURE_LEVEL_0) + string(REGEX REPLACE "[.]filamat$" "_fl0.filamat" output_path_fl0 ${output_path}) + add_custom_command( + 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_fl0}" + ) + 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 -a opengl -p ${MATC_TARGET} ${MATC_OPT_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 ${mat_src} to ${output_path}" + COMMENT "Compiling material ${fullname} to ${output_path_multiview}" ) - list(APPEND MATERIAL_BINS ${output_path}) - endforeach () -endif () + list(APPEND MATERIAL_BINS ${output_path_multiview}) + endif () + +endforeach() # Additional dependencies on included files for materials diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 125a2a780f2..0a84de4fbc9 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -71,6 +71,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMEN include/backend/platforms/OpenGLPlatform.h src/opengl/gl_headers.cpp src/opengl/gl_headers.h + src/opengl/GLBufferObject.h + src/opengl/GLTexture.h src/opengl/GLUtils.cpp src/opengl/GLUtils.h src/opengl/OpenGLBlobCache.cpp @@ -133,6 +135,7 @@ if (FILAMENT_SUPPORTS_METAL) src/metal/MetalExternalImage.mm src/metal/MetalHandles.mm src/metal/MetalPlatform.mm + src/metal/MetalShaderCompiler.mm src/metal/MetalState.mm src/metal/MetalTimerQuery.mm src/metal/MetalUtils.mm @@ -165,9 +168,15 @@ endif() if (FILAMENT_SUPPORTS_VULKAN) list(APPEND SRCS include/backend/platforms/VulkanPlatform.h + src/vulkan/caching/VulkanDescriptorSetManager.cpp + src/vulkan/caching/VulkanDescriptorSetManager.h + src/vulkan/caching/VulkanPipelineLayoutCache.cpp + src/vulkan/caching/VulkanPipelineLayoutCache.h src/vulkan/platform/VulkanPlatform.cpp src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp src/vulkan/platform/VulkanPlatformSwapChainImpl.h + src/vulkan/spirv/VulkanSpirvUtils.cpp + src/vulkan/spirv/VulkanSpirvUtils.h src/vulkan/VulkanBlitter.cpp src/vulkan/VulkanBlitter.h src/vulkan/VulkanBuffer.cpp @@ -312,6 +321,7 @@ endif() if (FILAMENT_SUPPORTS_VULKAN) target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc vkshaders smol-v) + target_link_libraries(${TARGET} PRIVATE SPIRV-Headers) endif() if (FILAMENT_SUPPORTS_METAL) @@ -396,8 +406,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include) # ================================================================================================== option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF) -if (APPLE) - add_library(backend_test STATIC +if (APPLE OR LINUX) + set(BACKEND_TEST_SRC test/BackendTest.cpp test/ShaderGenerator.cpp test/TrianglePrimitive.cpp @@ -409,19 +419,26 @@ if (APPLE) test/test_BufferUpdates.cpp test/test_MRT.cpp test/test_LoadImage.cpp - test/test_RenderExternalImage.cpp test/test_StencilBuffer.cpp test/test_Scissor.cpp test/test_MipLevels.cpp - ) - - target_link_libraries(backend_test PRIVATE + ) + set(BACKEND_TEST_LIBS backend getopt gtest + imageio filamat SPIRV spirv-cross-glsl) +endif() + +if (APPLE) + # TODO: we should expand this test to Linux and other platforms. + list(APPEND BACKEND_TEST_SRC + test/test_RenderExternalImage.cpp) + add_library(backend_test STATIC ${BACKEND_TEST_SRC}) + target_link_libraries(backend_test PRIVATE ${BACKEND_TEST_LIBS}) set(BACKEND_TEST_DEPS OGLCompiler @@ -435,12 +452,11 @@ if (APPLE) glslang spirv-cross-core spirv-cross-glsl - spirv-cross-msl - ) + spirv-cross-msl) if (NOT IOS) target_link_libraries(backend_test PRIVATE image imageio) - list(APPEND BACKEND_TEST_DEPS image imageio) + list(APPEND BACKEND_TEST_DEPS image) endif() set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a") @@ -454,15 +470,21 @@ if (APPLE) endif() set_target_properties(backend_test PROPERTIES FOLDER Tests) + + if (APPLE AND NOT IOS) + add_executable(backend_test_mac test/mac_runner.mm) + target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore") + # Because each test case is a separate file, the -force_load flag is necessary to prevent the + # linker from removing "unused" symbols. + target_link_libraries(backend_test_mac PRIVATE -force_load backend_test) + set_target_properties(backend_test_mac PROPERTIES FOLDER Tests) + endif() endif() -if (APPLE AND NOT IOS) - add_executable(backend_test_mac test/mac_runner.mm) - target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore") - # Because each test case is a separate file, the -force_load flag is necessary to prevent the - # linker from removing "unused" symbols. - target_link_libraries(backend_test_mac PRIVATE -force_load backend_test) - set_target_properties(backend_test_mac PROPERTIES FOLDER Tests) +if (LINUX) + add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC}) + target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS}) + set_target_properties(backend_test_linux PROPERTIES FOLDER Tests) endif() # ================================================================================================== diff --git a/filament/backend/include/backend/BufferDescriptor.h b/filament/backend/include/backend/BufferDescriptor.h index 80fe182abb2..ebb57537cc9 100644 --- a/filament/backend/include/backend/BufferDescriptor.h +++ b/filament/backend/include/backend/BufferDescriptor.h @@ -23,7 +23,6 @@ #include #include -#include namespace filament::backend { @@ -113,7 +112,7 @@ class UTILS_PUBLIC BufferDescriptor { /** * Helper to create a BufferDescriptor that uses a KNOWN method pointer w/ object passed * by pointer as the callback. e.g.: - * auto bd = BufferDescriptor::make(buffer, size, &Foo::method, foo); + * auto bd = BufferDescriptor::make(buffer, size, foo); * * @param buffer Memory address of the CPU buffer to reference * @param size Size of the CPU buffer in bytes @@ -121,12 +120,12 @@ class UTILS_PUBLIC BufferDescriptor { * @return a new BufferDescriptor */ template - static BufferDescriptor make( - void const* buffer, size_t size, T* data, CallbackHandler* handler = nullptr) noexcept { + static BufferDescriptor make(void const* buffer, size_t size, T* data, + CallbackHandler* handler = nullptr) noexcept { return { buffer, size, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); + (static_cast(u)->*method)(b, s); }, data }; } @@ -145,14 +144,14 @@ class UTILS_PUBLIC BufferDescriptor { * @return a new BufferDescriptor */ template - static BufferDescriptor make( - void const* buffer, size_t size, T&& functor, CallbackHandler* handler = nullptr) noexcept { + static BufferDescriptor make(void const* buffer, size_t size, T&& functor, + CallbackHandler* handler = nullptr) noexcept { return { buffer, size, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; @@ -201,7 +200,7 @@ class UTILS_PUBLIC BufferDescriptor { return mUser; } - //! CPU mempry-buffer virtual address + //! CPU memory-buffer virtual address void* buffer = nullptr; //! CPU memory-buffer size in bytes diff --git a/filament/backend/include/backend/CallbackHandler.h b/filament/backend/include/backend/CallbackHandler.h index 3ffc707cdd1..036031a9d09 100644 --- a/filament/backend/include/backend/CallbackHandler.h +++ b/filament/backend/include/backend/CallbackHandler.h @@ -17,8 +17,6 @@ #ifndef TNT_FILAMENT_BACKEND_CALLBACKHANDLER_H #define TNT_FILAMENT_BACKEND_CALLBACKHANDLER_H -#include - namespace filament::backend { /** diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 227759769d4..6b0424ca063 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -28,7 +28,8 @@ #include -#include // FIXME: STL headers are not allowed in public headers +#include // FIXME: STL headers are not allowed in public headers +#include // FIXME: STL headers are not allowed in public headers #include #include @@ -80,7 +81,14 @@ static constexpr uint64_t SWAP_CHAIN_CONFIG_SRGB_COLORSPACE = 0x10; /** * Indicates that the SwapChain should also contain a stencil component. */ -static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = 0x20; +static constexpr uint64_t SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER = 0x20; +static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER; + +/** + * The SwapChain contains protected content. Currently only supported by OpenGLPlatform and + * only when OpenGLPlatform::isProtectedContextSupported() is true. + */ +static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40; static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES. @@ -128,6 +136,12 @@ enum class Backend : uint8_t { NOOP = 4, //!< Selects the no-op driver for testing purposes. }; +enum class TimerQueryResult : int8_t { + ERROR = -1, // an error occurred, result won't be available + NOT_READY = 0, // result to ready yet + AVAILABLE = 1, // result is available +}; + static constexpr const char* backendToString(Backend backend) { switch (backend) { case Backend::NOOP: @@ -154,6 +168,19 @@ enum class ShaderLanguage { MSL = 3, }; +static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) { + switch (shaderLanguage) { + case ShaderLanguage::ESSL1: + return "ESSL 1.0"; + case ShaderLanguage::ESSL3: + return "ESSL 3.0"; + case ShaderLanguage::SPIRV: + return "SPIR-V"; + case ShaderLanguage::MSL: + return "MSL"; + } +} + /** * Bitmask for selecting render buffers */ @@ -214,6 +241,7 @@ struct Viewport { int32_t top() const noexcept { return bottom + int32_t(height); } }; + /** * Specifies the mapping of the near and far clipping plane to window coordinates. */ @@ -644,14 +672,17 @@ enum class TextureFormat : uint16_t { }; //! Bitmask describing the intended Texture Usage -enum class TextureUsage : uint8_t { - NONE = 0x0, - COLOR_ATTACHMENT = 0x1, //!< Texture can be used as a color attachment - DEPTH_ATTACHMENT = 0x2, //!< Texture can be used as a depth attachment - STENCIL_ATTACHMENT = 0x4, //!< Texture can be used as a stencil attachment - UPLOADABLE = 0x8, //!< Data can be uploaded into this texture (default) - SAMPLEABLE = 0x10, //!< Texture can be sampled (default) - SUBPASS_INPUT = 0x20, //!< Texture can be used as a subpass input +enum class TextureUsage : uint16_t { + NONE = 0x0000, + COLOR_ATTACHMENT = 0x0001, //!< Texture can be used as a color attachment + DEPTH_ATTACHMENT = 0x0002, //!< Texture can be used as a depth attachment + STENCIL_ATTACHMENT = 0x0004, //!< Texture can be used as a stencil attachment + UPLOADABLE = 0x0008, //!< Data can be uploaded into this texture (default) + SAMPLEABLE = 0x0010, //!< Texture can be sampled (default) + SUBPASS_INPUT = 0x0020, //!< Texture can be used as a subpass input + BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit() + BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit() + PROTECTED = 0x0100, //!< Texture can be used the destination of a blit() DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage }; @@ -679,6 +710,17 @@ static constexpr bool isDepthFormat(TextureFormat format) noexcept { } } +static constexpr bool isStencilFormat(TextureFormat format) noexcept { + switch (format) { + case TextureFormat::STENCIL8: + case TextureFormat::DEPTH24_STENCIL8: + case TextureFormat::DEPTH32F_STENCIL8: + return true; + default: + return false; + } +} + static constexpr bool isUnsignedIntFormat(TextureFormat format) { switch (format) { case TextureFormat::R8UI: @@ -1144,11 +1186,27 @@ struct StencilState { //! Stencil operations for front-facing polygons StencilOperations front = { - .stencilFunc = StencilFunction::A, .ref = 0, .readMask = 0xff, .writeMask = 0xff }; + .stencilFunc = StencilFunction::A, + .stencilOpStencilFail = StencilOperation::KEEP, + .padding0 = 0, + .stencilOpDepthFail = StencilOperation::KEEP, + .stencilOpDepthStencilPass = StencilOperation::KEEP, + .padding1 = 0, + .ref = 0, + .readMask = 0xff, + .writeMask = 0xff }; //! Stencil operations for back-facing polygons StencilOperations back = { - .stencilFunc = StencilFunction::A, .ref = 0, .readMask = 0xff, .writeMask = 0xff }; + .stencilFunc = StencilFunction::A, + .stencilOpStencilFail = StencilOperation::KEEP, + .padding0 = 0, + .stencilOpDepthFail = StencilOperation::KEEP, + .stencilOpDepthStencilPass = StencilOperation::KEEP, + .padding1 = 0, + .ref = 0, + .readMask = 0xff, + .writeMask = 0xff }; //! Whether stencil-buffer writes are enabled bool stencilWrite = false; @@ -1167,8 +1225,8 @@ using FrameScheduledCallback = void(*)(PresentCallable callable, void* user); enum class Workaround : uint16_t { // The EASU pass must split because shader compiler flattens early-exit branch SPLIT_EASU, - // Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're read-only for - // the whole render pass. + // Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're + // read-only for the whole render pass. ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP, // for some uniform arrays, it's needed to do an initialization to avoid crash on adreno gpu ADRENO_UNIFORM_ARRAY_CRASH, @@ -1184,6 +1242,14 @@ enum class Workaround : uint16_t { DISABLE_THREAD_AFFINITY }; +//! The type of technique for stereoscopic rendering +enum class StereoscopicType : uint8_t { + // Stereoscopic rendering is performed using instanced rendering technique. + INSTANCED, + // Stereoscopic rendering is performed using the multiview feature from the graphics backend. + MULTIVIEW, +}; + } // namespace filament::backend template<> struct utils::EnableBitMaskOperators diff --git a/filament/backend/include/backend/Handle.h b/filament/backend/include/backend/Handle.h index 04e83809800..4b63607a1cf 100644 --- a/filament/backend/include/backend/Handle.h +++ b/filament/backend/include/backend/Handle.h @@ -17,16 +17,14 @@ #ifndef TNT_FILAMENT_BACKEND_HANDLE_H #define TNT_FILAMENT_BACKEND_HANDLE_H -#include #if !defined(NDEBUG) -#include +#include #endif #include -#include +#include // FIXME: STL headers are not allowed in public headers -#include -#include +#include namespace filament::backend { @@ -41,6 +39,7 @@ struct HwStream; struct HwSwapChain; struct HwTexture; struct HwTimerQuery; +struct HwVertexBufferInfo; struct HwVertexBuffer; /* @@ -54,7 +53,7 @@ struct HwVertexBuffer; class HandleBase { public: using HandleId = uint32_t; - static constexpr const HandleId nullid = HandleId{ std::numeric_limits::max() }; + static constexpr const HandleId nullid = HandleId{ UINT32_MAX }; constexpr HandleBase() noexcept: object(nullid) {} @@ -64,14 +63,6 @@ class HandleBase { // clear the handle, this doesn't free associated resources void clear() noexcept { object = nullid; } - // compare handles - bool operator==(const HandleBase& rhs) const noexcept { return object == rhs.object; } - bool operator!=(const HandleBase& rhs) const noexcept { return object != rhs.object; } - bool operator<(const HandleBase& rhs) const noexcept { return object < rhs.object; } - bool operator<=(const HandleBase& rhs) const noexcept { return object <= rhs.object; } - bool operator>(const HandleBase& rhs) const noexcept { return object > rhs.object; } - bool operator>=(const HandleBase& rhs) const noexcept { return object >= rhs.object; } - // get this handle's handleId HandleId getId() const noexcept { return object; } @@ -103,6 +94,14 @@ struct Handle : public HandleBase { explicit Handle(HandleId id) noexcept : HandleBase(id) { } + // compare handles of the same type + bool operator==(const Handle& rhs) const noexcept { return getId() == rhs.getId(); } + bool operator!=(const Handle& rhs) const noexcept { return getId() != rhs.getId(); } + bool operator<(const Handle& rhs) const noexcept { return getId() < rhs.getId(); } + bool operator<=(const Handle& rhs) const noexcept { return getId() <= rhs.getId(); } + bool operator>(const Handle& rhs) const noexcept { return getId() > rhs.getId(); } + bool operator>=(const Handle& rhs) const noexcept { return getId() >= rhs.getId(); } + // type-safe Handle cast template::value> > Handle(Handle const& base) noexcept : HandleBase(base) { } // NOLINT(hicpp-explicit-conversions,google-explicit-constructor) @@ -116,18 +115,19 @@ struct Handle : public HandleBase { // Types used by the command stream // (we use this renaming because the macro-system doesn't deal well with "<" and ">") -using BufferObjectHandle = Handle; -using FenceHandle = Handle; -using IndexBufferHandle = Handle; -using ProgramHandle = Handle; -using RenderPrimitiveHandle = Handle; -using RenderTargetHandle = Handle; -using SamplerGroupHandle = Handle; -using StreamHandle = Handle; -using SwapChainHandle = Handle; -using TextureHandle = Handle; -using TimerQueryHandle = Handle; -using VertexBufferHandle = Handle; +using BufferObjectHandle = Handle; +using FenceHandle = Handle; +using IndexBufferHandle = Handle; +using ProgramHandle = Handle; +using RenderPrimitiveHandle = Handle; +using RenderTargetHandle = Handle; +using SamplerGroupHandle = Handle; +using StreamHandle = Handle; +using SwapChainHandle = Handle; +using TextureHandle = Handle; +using TimerQueryHandle = Handle; +using VertexBufferHandle = Handle; +using VertexBufferInfoHandle = Handle; } // namespace filament::backend diff --git a/filament/backend/include/backend/PipelineState.h b/filament/backend/include/backend/PipelineState.h index 8b31c5f3dfd..220d04bbf26 100644 --- a/filament/backend/include/backend/PipelineState.h +++ b/filament/backend/include/backend/PipelineState.h @@ -20,7 +20,7 @@ #include #include -#include +#include #include @@ -29,14 +29,13 @@ namespace filament::backend { //! \privatesection struct PipelineState { - Handle program; - RasterState rasterState; - StencilState stencilState; - PolygonOffset polygonOffset; - Viewport scissor{ 0, 0, - (uint32_t)std::numeric_limits::max(), - (uint32_t)std::numeric_limits::max() - }; + Handle program; // 4 + Handle vertexBufferInfo; // 4 + RasterState rasterState; // 4 + StencilState stencilState; // 12 + PolygonOffset polygonOffset; // 8 + PrimitiveType primitiveType = PrimitiveType::TRIANGLES; // 1 + uint8_t padding[3] = {}; // 3 }; } // namespace filament::backend diff --git a/filament/backend/include/backend/PixelBufferDescriptor.h b/filament/backend/include/backend/PixelBufferDescriptor.h index 1b498032fdc..c45f344d02a 100644 --- a/filament/backend/include/backend/PixelBufferDescriptor.h +++ b/filament/backend/include/backend/PixelBufferDescriptor.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -141,7 +142,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, alignment, left, top, stride, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); }, data }; + (static_cast(u)->*method)(b, s); }, data }; } template @@ -149,7 +150,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { PixelDataFormat format, PixelDataType type, T* data, CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); }, data }; + (static_cast(u)->*method)(b, s); }, data }; } template @@ -157,7 +158,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { backend::CompressedPixelDataType format, uint32_t imageSize, T* data, CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, imageSize, handler, [](void* b, size_t s, void* u) { - (*static_cast(u)->*method)(b, s); }, data + (static_cast(u)->*method)(b, s); }, data }; } @@ -168,9 +169,9 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, alignment, left, top, stride, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; } @@ -181,9 +182,9 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, type, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; } @@ -194,9 +195,9 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { CallbackHandler* handler = nullptr) noexcept { return { buffer, size, format, imageSize, handler, [](void* b, size_t s, void* u) { - T& that = *static_cast(u); - that(b, s); - delete &that; + T* const that = static_cast(u); + that->operator()(b, s); + delete that; }, new T(std::forward(functor)) }; } diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index 1777860f86c..03026dffe6e 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -19,11 +19,12 @@ #ifndef TNT_FILAMENT_BACKEND_PLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORM_H -#include - #include #include +#include +#include + namespace filament::backend { class Driver; @@ -41,11 +42,29 @@ class UTILS_PUBLIC Platform { struct Stream {}; struct DriverConfig { - /* - * size of handle arena in bytes. Setting to 0 indicates default value is to be used. + /** + * Size of handle arena in bytes. Setting to 0 indicates default value is to be used. * Driver clamps to valid values. */ size_t handleArenaSize = 0; + + /** + * This number of most-recently destroyed textures will be tracked for use-after-free. + * Throws an exception when a texture is freed but still bound to a SamplerGroup and used in + * a draw call. 0 disables completely. Currently only respected by the Metal backend. + */ + size_t textureUseAfterFreePoolSize = 0; + + /** + * Set to `true` to forcibly disable parallel shader compilation in the backend. + * Currently only honored by the GL and Metal backends. + */ + bool disableParallelShaderCompile = false; + + /** + * Disable backend handles use-after-free checks. + */ + bool disableHandleUseAfterFreeCheck = false; }; Platform() noexcept; @@ -71,7 +90,7 @@ class UTILS_PUBLIC Platform { * * @return nullptr on failure, or a pointer to the newly created driver. */ - virtual backend::Driver* createDriver(void* sharedContext, + virtual backend::Driver* UTILS_NULLABLE createDriver(void* UTILS_NULLABLE sharedContext, const DriverConfig& driverConfig) noexcept = 0; /** @@ -89,7 +108,8 @@ class UTILS_PUBLIC Platform { * cache. */ using InsertBlobFunc = utils::Invocable< - void(const void* key, size_t keySize, const void* value, size_t valueSize)>; + void(const void* UTILS_NONNULL key, size_t keySize, + const void* UTILS_NONNULL value, size_t valueSize)>; /* * RetrieveBlobFunc is an Invocable to an application-provided function that a @@ -97,7 +117,8 @@ class UTILS_PUBLIC Platform { * cache. */ using RetrieveBlobFunc = utils::Invocable< - size_t(const void* key, size_t keySize, void* value, size_t valueSize)>; + size_t(const void* UTILS_NONNULL key, size_t keySize, + void* UTILS_NONNULL value, size_t valueSize)>; /** * Sets the callback functions that the backend can use to interact with caching functionality @@ -107,6 +128,7 @@ class UTILS_PUBLIC Platform { * Platform. The and Invocables may be called at any time and * from any thread from the time at which setBlobFunc is called until the time that Platform * is destroyed. Concurrent calls to these functions from different threads is also allowed. + * Either function can be null. * * @param insertBlob an Invocable that inserts a new value into the cache and associates * it with the given key @@ -116,9 +138,21 @@ class UTILS_PUBLIC Platform { void setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept; /** - * @return true if setBlobFunc was called. + * @return true if insertBlob is valid. + */ + bool hasInsertBlobFunc() const noexcept; + + /** + * @return true if retrieveBlob is valid. + */ + bool hasRetrieveBlobFunc() const noexcept; + + /** + * @return true if either of insertBlob or retrieveBlob are valid. */ - bool hasBlobFunc() const noexcept; + bool hasBlobFunc() const noexcept { + return hasInsertBlobFunc() || hasRetrieveBlobFunc(); + } /** * To insert a new binary value into the cache and associate it with a given @@ -137,7 +171,8 @@ class UTILS_PUBLIC Platform { * @param value pointer to the beginning of the value data that is to be inserted * @param valueSize specifies the size in byte of the data pointed to by */ - void insertBlob(const void* key, size_t keySize, const void* value, size_t valueSize); + void insertBlob(const void* UTILS_NONNULL key, size_t keySize, + const void* UTILS_NONNULL value, size_t valueSize); /** * To retrieve the binary value associated with a given key from the cache, a @@ -156,11 +191,43 @@ class UTILS_PUBLIC Platform { * @return If the cache contains a value associated with the given key then the * size of that binary value in bytes is returned. Otherwise 0 is returned. */ - size_t retrieveBlob(const void* key, size_t keySize, void* value, size_t valueSize); + 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/PresentCallable.h b/filament/backend/include/backend/PresentCallable.h index 26579420cb0..4402f22266d 100644 --- a/filament/backend/include/backend/PresentCallable.h +++ b/filament/backend/include/backend/PresentCallable.h @@ -21,8 +21,7 @@ #include -namespace filament { -namespace backend { +namespace filament::backend { /** * A PresentCallable is a callable object that, when called, schedules a frame for presentation on @@ -98,7 +97,6 @@ class UTILS_PUBLIC PresentCallable { */ using FrameFinishedCallback UTILS_DEPRECATED = void(*)(PresentCallable callable, void* user); -} // namespace backend -} // namespace filament +} // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_PRESENTCALLABLE diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h index a27790c2cbe..fe1c4a9b6e8 100644 --- a/filament/backend/include/backend/Program.h +++ b/filament/backend/include/backend/Program.h @@ -17,17 +17,19 @@ #ifndef TNT_FILAMENT_BACKEND_PRIVATE_PROGRAM_H #define TNT_FILAMENT_BACKEND_PRIVATE_PROGRAM_H -#include #include #include #include -#include #include #include -#include -#include +#include // FIXME: STL headers are not allowed in public headers +#include // FIXME: STL headers are not allowed in public headers +#include // FIXME: STL headers are not allowed in public headers + +#include +#include namespace filament::backend { @@ -114,6 +116,8 @@ class Program { Program& cacheId(uint64_t cacheId) noexcept; + Program& multiview(bool multiview) noexcept; + ShaderSource const& getShadersSource() const noexcept { return mShadersSource; } ShaderSource& getShadersSource() noexcept { return mShadersSource; } @@ -141,6 +145,8 @@ class Program { uint64_t getCacheId() const noexcept { return mCacheId; } + bool isMultiview() const noexcept { return mMultiview; } + CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; } private: @@ -156,6 +162,11 @@ class Program { utils::FixedCapacityVector> mAttributes; std::array mBindingUniformInfo; CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH; + // Indicates the current engine was initialized with multiview stereo, and the variant for this + // program contains STE flag. This will be referred later for the OpenGL shader compiler to + // determine whether shader code replacement for the num_views should be performed. + // This variable could be promoted as a more generic variable later if other similar needs occur. + bool mMultiview = false; }; } // namespace filament::backend diff --git a/filament/backend/include/backend/SamplerDescriptor.h b/filament/backend/include/backend/SamplerDescriptor.h index fe78d6100c3..f99472daef3 100644 --- a/filament/backend/include/backend/SamplerDescriptor.h +++ b/filament/backend/include/backend/SamplerDescriptor.h @@ -24,9 +24,6 @@ #include -#include -#include - namespace filament::backend { struct UTILS_PUBLIC SamplerDescriptor { diff --git a/filament/backend/include/backend/TargetBufferInfo.h b/filament/backend/include/backend/TargetBufferInfo.h index a2f30d45394..ce23fc5fd53 100644 --- a/filament/backend/include/backend/TargetBufferInfo.h +++ b/filament/backend/include/backend/TargetBufferInfo.h @@ -17,9 +17,11 @@ #ifndef TNT_FILAMENT_BACKEND_TARGETBUFFERINFO_H #define TNT_FILAMENT_BACKEND_TARGETBUFFERINFO_H -#include #include +#include + +#include #include namespace filament::backend { @@ -30,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; @@ -78,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 3f4488c5f53..dec6f47ba74 100644 --- a/filament/backend/include/backend/platforms/OpenGLPlatform.h +++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h @@ -18,8 +18,15 @@ #define TNT_FILAMENT_BACKEND_PRIVATE_OPENGLPLATFORM_H #include +#include #include +#include +#include + +#include +#include + namespace filament::backend { class Driver; @@ -38,8 +45,8 @@ class OpenGLPlatform : public Platform { * Derived classes can use this to instantiate the default OpenGLDriver backend. * This is typically called from your implementation of createDriver() */ - static Driver* createDefaultDriver(OpenGLPlatform* platform, - void* sharedContext, const DriverConfig& driverConfig); + static Driver* UTILS_NULLABLE createDefaultDriver(OpenGLPlatform* UTILS_NONNULL platform, + void* UTILS_NULLABLE sharedContext, const DriverConfig& driverConfig); ~OpenGLPlatform() noexcept override; @@ -57,6 +64,22 @@ class OpenGLPlatform : public Platform { */ virtual void terminate() noexcept = 0; + /** + * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. + * The default implementation returns false. + * + * @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise. + */ + virtual bool isSRGBSwapChainSupported() const noexcept; + + /** + * Return whether protected contexts are supported by this backend. + * If protected context are supported, the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag can be + * used when creating a SwapChain. + * The default implementation returns false. + */ + virtual bool isProtectedContextSupported() const noexcept; + /** * Called by the driver to create a SwapChain for this driver. * @@ -66,15 +89,8 @@ class OpenGLPlatform : public Platform { * @return The driver's SwapChain object. * */ - virtual SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) noexcept = 0; - - /** - * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. - * The default implementation returns false. - * - * @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise. - */ - virtual bool isSRGBSwapChainSupported() const noexcept; + virtual SwapChain* UTILS_NULLABLE createSwapChain( + void* UTILS_NULLABLE nativeWindow, uint64_t flags) noexcept = 0; /** * Called by the driver create a headless SwapChain. @@ -87,13 +103,14 @@ class OpenGLPlatform : public Platform { * TODO: we need a more generic way of passing construction parameters * A void* might be enough. */ - virtual SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept = 0; + virtual SwapChain* UTILS_NULLABLE createSwapChain( + uint32_t width, uint32_t height, uint64_t flags) noexcept = 0; /** * Called by the driver to destroys the SwapChain * @param swapChain SwapChain to be destroyed. */ - virtual void destroySwapChain(SwapChain* swapChain) noexcept = 0; + virtual void destroySwapChain(SwapChain* UTILS_NONNULL swapChain) noexcept = 0; /** * Returns the set of buffers that must be preserved up to the call to commit(). @@ -106,28 +123,80 @@ class OpenGLPlatform : public Platform { * @return buffer that must be preserved * @see commit() */ - virtual TargetBufferFlags getPreservedFlags(SwapChain* swapChain) noexcept; + 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; + + + /** + * 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 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(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`) + * @param preContextChange called before the context changes + * @param postContextChange called after the context changes */ - virtual void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept = 0; + virtual void makeCurrent( + SwapChain* UTILS_NONNULL drawSwapChain, + SwapChain* UTILS_NONNULL readSwapChain, + utils::Invocable preContextChange, + utils::Invocable postContextChange) noexcept; /** * Called by the driver once the current frame finishes drawing. Typically, this should present * the drawSwapChain. This is for example where `eglMakeCurrent()` would be called. * @param swapChain the SwapChain to present. */ - virtual void commit(SwapChain* swapChain) noexcept = 0; + virtual void commit(SwapChain* UTILS_NONNULL swapChain) noexcept = 0; /** * Set the time the next committed buffer should be presented to the user at. @@ -152,14 +221,14 @@ class OpenGLPlatform : public Platform { * * @return A Fence object. The default implementation returns nullptr. */ - virtual Fence* createFence() noexcept; + virtual Fence* UTILS_NULLABLE createFence() noexcept; /** * Destroys a Fence object. The default implementation does nothing. * * @param fence Fence to destroy. */ - virtual void destroyFence(Fence* fence) noexcept; + virtual void destroyFence(Fence* UTILS_NONNULL fence) noexcept; /** * Waits on a Fence. @@ -169,7 +238,7 @@ class OpenGLPlatform : public Platform { * @return Whether the fence signaled or timed out. See backend::FenceStatus. * The default implementation always return backend::FenceStatus::ERROR. */ - virtual backend::FenceStatus waitFence(Fence* fence, uint64_t timeout) noexcept; + virtual backend::FenceStatus waitFence(Fence* UTILS_NONNULL fence, uint64_t timeout) noexcept; // -------------------------------------------------------------------------------------------- @@ -183,13 +252,13 @@ class OpenGLPlatform : public Platform { * @param nativeStream The native stream, this parameter depends on the concrete implementation. * @return A new Stream object. */ - virtual Stream* createStream(void* nativeStream) noexcept; + virtual Stream* UTILS_NULLABLE createStream(void* UTILS_NULLABLE nativeStream) noexcept; /** * Destroys a Stream. * @param stream Stream to destroy. */ - virtual void destroyStream(Stream* stream) noexcept; + virtual void destroyStream(Stream* UTILS_NONNULL stream) noexcept; /** * The specified stream takes ownership of the texture (tname) object @@ -199,20 +268,21 @@ class OpenGLPlatform : public Platform { * @param stream Stream to take ownership of the texture * @param tname GL texture id to "bind" to the Stream. */ - virtual void attach(Stream* stream, intptr_t tname) noexcept; + virtual void attach(Stream* UTILS_NONNULL stream, intptr_t tname) noexcept; /** * Destroys the texture associated to the stream * @param stream Stream to detach from its texture */ - virtual void detach(Stream* stream) noexcept; + virtual void detach(Stream* UTILS_NONNULL stream) noexcept; /** * Updates the content of the texture attached to the stream. * @param stream Stream to update * @param timestamp Output parameter: Timestamp of the image bound to the texture. */ - virtual void updateTexImage(Stream* stream, int64_t* timestamp) noexcept; + virtual void updateTexImage(Stream* UTILS_NONNULL stream, + int64_t* UTILS_NONNULL timestamp) noexcept; // -------------------------------------------------------------------------------------------- @@ -225,13 +295,13 @@ class OpenGLPlatform : public Platform { * implementation could just return { 0, GL_TEXTURE_2D } at this point. The actual * values can be delayed until setExternalImage. */ - virtual ExternalTexture *createExternalImageTexture() noexcept; + virtual ExternalTexture* UTILS_NULLABLE createExternalImageTexture() noexcept; /** * Destroys an external texture handle and associated data. * @param texture a pointer to the handle to destroy. */ - virtual void destroyExternalImage(ExternalTexture* texture) noexcept; + virtual void destroyExternalImage(ExternalTexture* UTILS_NONNULL texture) noexcept; // called on the application thread to allow Filament to take ownership of the image @@ -244,7 +314,7 @@ class OpenGLPlatform : public Platform { * @param externalImage A token representing the platform's external image. * @see destroyExternalImage */ - virtual void retainExternalImage(void* externalImage) noexcept; + virtual void retainExternalImage(void* UTILS_NONNULL externalImage) noexcept; /** * Called to bind the platform-specific externalImage to an ExternalTexture. @@ -258,7 +328,8 @@ class OpenGLPlatform : public Platform { * @param texture an in/out pointer to ExternalTexture, id and target can be updated if necessary. * @return true on success, false on error. */ - virtual bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept; + virtual bool setExternalImage(void* UTILS_NONNULL externalImage, + ExternalTexture* UTILS_NONNULL texture) noexcept; /** * The method allows platforms to convert a user-supplied external image object into a new type diff --git a/filament/backend/include/backend/platforms/PlatformCocoaGL.h b/filament/backend/include/backend/platforms/PlatformCocoaGL.h index df03bcbf290..97c9c3ce892 100644 --- a/filament/backend/include/backend/platforms/PlatformCocoaGL.h +++ b/filament/backend/include/backend/platforms/PlatformCocoaGL.h @@ -17,11 +17,10 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H -#include - +#include #include -#include +#include namespace filament::backend { @@ -58,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 cbdd6a066a6..e7f1d1ffe44 100644 --- a/filament/backend/include/backend/platforms/PlatformCocoaTouchGL.h +++ b/filament/backend/include/backend/platforms/PlatformCocoaTouchGL.h @@ -30,7 +30,7 @@ struct PlatformCocoaTouchGLImpl; class PlatformCocoaTouchGL : public OpenGLPlatform { public: PlatformCocoaTouchGL(); - ~PlatformCocoaTouchGL() noexcept; + ~PlatformCocoaTouchGL() noexcept override; // -------------------------------------------------------------------------------------------- // Platform Interface @@ -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 79400540063..ef6876536b1 100644 --- a/filament/backend/include/backend/platforms/PlatformEGL.h +++ b/filament/backend/include/backend/platforms/PlatformEGL.h @@ -17,18 +17,23 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_H -#include +#include +#include +#include #include #include +#include -#include - -#include +#include +#include #include #include +#include +#include + namespace filament::backend { /** @@ -38,12 +43,11 @@ class PlatformEGL : public OpenGLPlatform { public: PlatformEGL() noexcept; - bool isExtraContextSupported() const noexcept override; - void createContext(bool shared) override; - void releaseContext() noexcept override; -protected: + // Return true if we're on an OpenGL platform (as opposed to OpenGL ES). false by default. + virtual bool isOpenGL() const noexcept; +protected: // -------------------------------------------------------------------------------------------- // Helper for EGL configs and attributes parameters @@ -83,13 +87,30 @@ class PlatformEGL : public OpenGLPlatform { // -------------------------------------------------------------------------------------------- // OpenGLPlatform Interface + bool isExtraContextSupported() const noexcept override; + void createContext(bool shared) override; + void releaseContext() noexcept override; + void terminate() noexcept override; + bool isProtectedContextSupported() const noexcept override; + bool isSRGBSwapChainSupported() const noexcept override; SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) 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, + utils::Invocable preContextChange, + utils::Invocable postContextChange) noexcept override; + void commit(SwapChain* swapChain) noexcept override; bool canCreateFence() noexcept override; @@ -116,16 +137,28 @@ class PlatformEGL : public OpenGLPlatform { static void clearGlError() noexcept; /** - * Always use this instead of eglMakeCurrent(). + * Always use this instead of eglMakeCurrent(), as it tracks some state. */ - EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept; + + 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; - EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE; - EGLSurface mCurrentReadSurface = EGL_NO_SURFACE; + EGLContext mEGLContextProtected = 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; std::vector mAdditionalContexts; @@ -141,13 +174,38 @@ class PlatformEGL : public OpenGLPlatform { bool KHR_gl_colorspace = false; bool KHR_no_config_context = false; bool KHR_surfaceless_context = false; + bool EXT_protected_content = false; } egl; } ext; + struct SwapChainEGL : public Platform::SwapChain { + EGLSurface sur = EGL_NO_SURFACE; + Config attribs{}; + EGLNativeWindowType nativeWindow{}; + EGLConfig config{}; + uint64_t flags{}; + }; + void initializeGlExtensions() noexcept; +protected: + EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; + private: - EGLConfig findSwapChainConfig(uint64_t flags) const; + 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/PlatformEGLAndroid.h b/filament/backend/include/backend/platforms/PlatformEGLAndroid.h index 1c7a4cd7b6b..32f830384d3 100644 --- a/filament/backend/include/backend/platforms/PlatformEGLAndroid.h +++ b/filament/backend/include/backend/platforms/PlatformEGLAndroid.h @@ -17,8 +17,14 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H +#include +#include +#include #include +#include +#include + namespace filament::backend { class ExternalStreamManagerAndroid; diff --git a/filament/backend/include/backend/platforms/PlatformEGLHeadless.h b/filament/backend/include/backend/platforms/PlatformEGLHeadless.h index 13d5fa0578e..40d285b9084 100644 --- a/filament/backend/include/backend/platforms/PlatformEGLHeadless.h +++ b/filament/backend/include/backend/platforms/PlatformEGLHeadless.h @@ -30,6 +30,9 @@ class PlatformEGLHeadless : public PlatformEGL { Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept override; + +protected: + bool isOpenGL() const noexcept override; }; } // namespace filament 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/backend/platforms/VulkanPlatform.h b/filament/backend/include/backend/platforms/VulkanPlatform.h index c940567a51e..6201d64464e 100644 --- a/filament/backend/include/backend/platforms/VulkanPlatform.h +++ b/filament/backend/include/backend/platforms/VulkanPlatform.h @@ -20,13 +20,18 @@ #include #include + #include #include #include +#include #include #include +#include +#include + namespace filament::backend { using SwapChain = Platform::SwapChain; diff --git a/filament/backend/include/private/backend/CircularBuffer.h b/filament/backend/include/private/backend/CircularBuffer.h index 60ddd73985c..7d2de52b009 100644 --- a/filament/backend/include/private/backend/CircularBuffer.h +++ b/filament/backend/include/private/backend/CircularBuffer.h @@ -17,7 +17,7 @@ #ifndef TNT_FILAMENT_BACKEND_PRIVATE_CIRCULARBUFFER_H #define TNT_FILAMENT_BACKEND_PRIVATE_CIRCULARBUFFER_H -#include +#include #include #include @@ -40,28 +40,36 @@ class CircularBuffer { ~CircularBuffer() noexcept; - // allocates 'size' bytes in the circular buffer and returns a pointer to the memory - // return the current head and moves it forward by size bytes - inline void* allocate(size_t size) noexcept { + static size_t getBlockSize() noexcept { return sPageSize; } + + // Total size of circular buffer. This is a constant. + size_t size() const noexcept { return mSize; } + + // Allocates `s` bytes in the circular buffer and returns a pointer to the memory. All + // allocations must not exceed size() bytes. + inline void* allocate(size_t s) noexcept { + // We can never allocate more that size(). + assert_invariant(getUsed() + s <= size()); char* const cur = static_cast(mHead); - mHead = cur + size; + mHead = cur + s; return cur; } - // Total size of circular buffer - size_t size() const noexcept { return mSize; } - - // returns true if the buffer is empty (e.g. after calling flush) + // Returns true if the buffer is empty, i.e.: no allocations were made since + // calling getBuffer(); bool empty() const noexcept { return mTail == mHead; } - void* getHead() const noexcept { return mHead; } - - void* getTail() const noexcept { return mTail; } + // Returns the size used since the last call to getBuffer() + size_t getUsed() const noexcept { return intptr_t(mHead) - intptr_t(mTail); } - // call at least once every getRequiredSize() bytes allocated from the buffer - void circularize() noexcept; - - static size_t getBlockSize() noexcept { return sPageSize; } + // Retrieves the current allocated range and frees it. It is the responsibility of the caller + // to make sure the returned range is no longer in use by the time allocate() allocates + // (size() - getUsed()) bytes. + struct Range { + void* tail; + void* head; + }; + Range getBuffer() noexcept; private: void* alloc(size_t size) noexcept; @@ -69,10 +77,10 @@ class CircularBuffer { // pointer to the beginning of the circular buffer (constant) void* mData = nullptr; - int mUsesAshmem = -1; + int mAshmemFd = -1; // size of the circular buffer (constant) - size_t mSize = 0; + size_t const mSize; // pointer to the beginning of recorded data void* mTail = nullptr; diff --git a/filament/backend/include/private/backend/CommandBufferQueue.h b/filament/backend/include/private/backend/CommandBufferQueue.h index b8dd21337f5..92bf7e1488c 100644 --- a/filament/backend/include/private/backend/CommandBufferQueue.h +++ b/filament/backend/include/private/backend/CommandBufferQueue.h @@ -19,19 +19,21 @@ #include "private/backend/CircularBuffer.h" -#include #include #include #include +#include +#include + namespace filament::backend { /* * A producer-consumer command queue that uses a CircularBuffer as main storage */ class CommandBufferQueue { - struct Slice { + struct Range { void* begin; void* end; }; @@ -44,29 +46,33 @@ class CommandBufferQueue { mutable utils::Mutex mLock; mutable utils::Condition mCondition; - mutable std::vector mCommandBuffersToExecute; + mutable std::vector mCommandBuffersToExecute; 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() { return mCircularBuffer; } + CircularBuffer& getCircularBuffer() noexcept { return mCircularBuffer; } + CircularBuffer const& getCircularBuffer() const noexcept { return mCircularBuffer; } + + size_t getCapacity() const noexcept { return mRequiredSize; } size_t getHighWatermark() const noexcept { return mHighWatermark; } // wait for commands to be available and returns an array containing these commands - std::vector waitForCommands() const; + std::vector waitForCommands() const; // return the memory used by this command buffer to the circular buffer // WARNING: releaseBuffer() must be called in sequence of the Slices returned by // waitForCommands() - void releaseBuffer(Slice const& buffer); + void releaseBuffer(Range const& buffer); // all commands buffers (Slices) written to this point are returned by waitForCommand(). This // call blocks until the CircularBuffer has at least mRequiredSize bytes available. @@ -75,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/CommandStream.h b/filament/backend/include/private/backend/CommandStream.h index 1901361542d..f722794b52c 100644 --- a/filament/backend/include/private/backend/CommandStream.h +++ b/filament/backend/include/private/backend/CommandStream.h @@ -32,11 +32,13 @@ #include #include +#include #include #include #include #include +#include #include #ifndef NDEBUG @@ -132,7 +134,7 @@ struct CommandType { public: template - static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) noexcept { + static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) { Command* self = static_cast(base); *next = align(sizeof(Command)); #if DEBUG_COMMAND_STREAM @@ -152,21 +154,21 @@ struct CommandType { } // placement new declared as "throw" to avoid the compiler's null-check - inline void* operator new(std::size_t size, void* ptr) { + inline void* operator new(std::size_t, void* ptr) { assert_invariant(ptr); return ptr; } }; }; -// convert an method of "class Driver" into a Command<> type +// convert a method of "class Driver" into a Command<> type #define COMMAND_TYPE(method) CommandType::Command<&Driver::method> // ------------------------------------------------------------------------------------------------ class CustomCommand : public CommandBase { std::function mCommand; - static void execute(Driver&, CommandBase* base, intptr_t* next) noexcept; + static void execute(Driver&, CommandBase* base, intptr_t* next); public: inline CustomCommand(CustomCommand&& rhs) = default; inline explicit CustomCommand(std::function cmd) @@ -211,6 +213,8 @@ class CommandStream { CommandStream(CommandStream const& rhs) noexcept = delete; CommandStream& operator=(CommandStream const& rhs) noexcept = delete; + CircularBuffer const& getCircularBuffer() const noexcept { return mCurrentBuffer; } + public: #define DECL_DRIVER_API(methodName, paramsDecl, params) \ inline void methodName(paramsDecl) { \ diff --git a/filament/backend/include/private/backend/Driver.h b/filament/backend/include/private/backend/Driver.h index a8c31ef4bf0..527052378e6 100644 --- a/filament/backend/include/private/backend/Driver.h +++ b/filament/backend/include/private/backend/Driver.h @@ -24,10 +24,12 @@ #include #include +#include #include #include +#include #include // Command debugging off. debugging virtuals are not called. @@ -74,7 +76,7 @@ class Driver { // the fn function will execute a batch of driver commands // this gives the driver a chance to wrap their execution in a meaningful manner // the default implementation simply calls fn - virtual void execute(std::function const& fn) noexcept; + virtual void execute(std::function const& fn); // This is called on debug build, or when enabled manually on the backend thread side. virtual void debugCommandBegin(CommandStream* cmds, diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index 37ddd4c6ba0..680a6bb0136 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -167,12 +167,15 @@ DECL_DRIVER_API_0(resetState) * ----------------------- */ -DECL_DRIVER_API_R_N(backend::VertexBufferHandle, createVertexBuffer, +DECL_DRIVER_API_R_N(backend::VertexBufferInfoHandle, createVertexBufferInfo, uint8_t, bufferCount, uint8_t, attributeCount, - uint32_t, vertexCount, backend::AttributeArray, attributes) +DECL_DRIVER_API_R_N(backend::VertexBufferHandle, createVertexBuffer, + uint32_t, vertexCount, + backend::VertexBufferInfoHandle, vbih) + DECL_DRIVER_API_R_N(backend::IndexBufferHandle, createIndexBuffer, backend::ElementType, elementType, uint32_t, indexCount, @@ -219,16 +222,12 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture, backend::TextureUsage, usage) DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup, - uint32_t, size) + uint32_t, size, utils::FixedSizeString<32>, debugName) DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive, backend::VertexBufferHandle, vbh, backend::IndexBufferHandle, ibh, - backend::PrimitiveType, pt, - uint32_t, offset, - uint32_t, minIndex, - uint32_t, maxIndex, - uint32_t, count) + backend::PrimitiveType, pt) DECL_DRIVER_API_R_N(backend::ProgramHandle, createProgram, backend::Program&&, program) @@ -240,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) @@ -264,6 +264,7 @@ DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery) */ DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh) +DECL_DRIVER_API_N(destroyVertexBufferInfo,backend::VertexBufferInfoHandle, vbih) DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh) DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh) DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph) @@ -298,14 +299,16 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported) -DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported) +DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported) +DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported) +DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported) +DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported) DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers) DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize) DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams) -DECL_DRIVER_API_SYNCHRONOUS_0(bool, canGenerateMipmaps) DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image) -DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime) +DECL_DRIVER_API_SYNCHRONOUS_N(backend::TimerQueryResult, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime) DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround) DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel) @@ -468,7 +471,7 @@ DECL_DRIVER_API_N(readBufferSubData, * -------------------- */ -DECL_DRIVER_API_N(blit, +DECL_DRIVER_API_N(blitDEPRECATED, backend::TargetBufferFlags, buffers, backend::RenderTargetHandle, dst, backend::Viewport, dstRect, @@ -476,15 +479,47 @@ DECL_DRIVER_API_N(blit, backend::Viewport, srcRect, backend::SamplerMagFilter, filter) +DECL_DRIVER_API_N(resolve, + backend::TextureHandle, dst, + uint8_t, dstLevel, uint8_t, dstLayer, + backend::TextureHandle, src, + uint8_t, srcLevel, uint8_t, srcLayer) + +DECL_DRIVER_API_N(blit, + backend::TextureHandle, dst, + uint8_t, dstLevel, uint8_t, dstLayer, + math::uint2, dstOrigin, + backend::TextureHandle, src, + uint8_t, srcLevel, uint8_t, srcLayer, + math::uint2, srcOrigin, + math::uint2, size) + +DECL_DRIVER_API_N(bindPipeline, + backend::PipelineState, state) + +DECL_DRIVER_API_N(bindRenderPrimitive, + backend::RenderPrimitiveHandle, rph) + +DECL_DRIVER_API_N(draw2, + uint32_t, indexOffset, + uint32_t, indexCount, + uint32_t, instanceCount) + DECL_DRIVER_API_N(draw, backend::PipelineState, state, backend::RenderPrimitiveHandle, rph, + uint32_t, indexOffset, + uint32_t, indexCount, uint32_t, instanceCount) + DECL_DRIVER_API_N(dispatchCompute, backend::ProgramHandle, program, math::uint3, workGroupCount) +DECL_DRIVER_API_N(scissor, + Viewport, scissor) + #pragma clang diagnostic pop diff --git a/filament/backend/include/private/backend/HandleAllocator.h b/filament/backend/include/private/backend/HandleAllocator.h index 95481f5484e..bcf4bfa902f 100644 --- a/filament/backend/include/private/backend/HandleAllocator.h +++ b/filament/backend/include/private/backend/HandleAllocator.h @@ -22,29 +22,34 @@ #include #include #include +#include +#include +#include + #include + +#include +#include +#include #include +#include -#if !defined(NDEBUG) && UTILS_HAS_RTTI -# define HANDLE_TYPE_SAFETY 1 -#else -# define HANDLE_TYPE_SAFETY 0 -#endif +#include +#include -#define HandleAllocatorGL HandleAllocator<16, 64, 208> -#define HandleAllocatorVK HandleAllocator<16, 64, 880> -#define HandleAllocatorMTL HandleAllocator<16, 64, 576> +#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB +#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB +#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB namespace filament::backend { /* * A utility class to efficiently allocate and manage Handle<> */ -template +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(); @@ -60,15 +65,10 @@ class HandleAllocator { * */ template - Handle allocateAndConstruct(ARGS&& ... args) noexcept { - Handle h{ allocateHandle() }; + Handle allocateAndConstruct(ARGS&& ... args) { + Handle h{ allocateHandle() }; D* addr = handle_cast(h); new(addr) D(std::forward(args)...); -#if HANDLE_TYPE_SAFETY - mLock.lock(); - mHandleTypeId[addr] = typeid(D).name(); - mLock.unlock(); -#endif return h; } @@ -84,13 +84,7 @@ class HandleAllocator { */ template Handle allocate() noexcept { - Handle h{ allocateHandle() }; -#if HANDLE_TYPE_SAFETY - D* addr = handle_cast(h); - mLock.lock(); - mHandleTypeId[addr] = typeid(D).name(); - mLock.unlock(); -#endif + Handle h{ allocateHandle() }; return h; } @@ -103,21 +97,14 @@ class HandleAllocator { */ template typename std::enable_if_t, D>* - destroyAndConstruct(Handle const& handle, ARGS&& ... args) noexcept { + destroyAndConstruct(Handle const& handle, ARGS&& ... args) { assert_invariant(handle); D* addr = handle_cast(const_cast&>(handle)); assert_invariant(addr); - // currently we implement construct<> with dtor+ctor, we could use operator= also // but all our dtors are trivial, ~D() is actually a noop. addr->~D(); new(addr) D(std::forward(args)...); - -#if HANDLE_TYPE_SAFETY - mLock.lock(); - mHandleTypeId[addr] = typeid(D).name(); - mLock.unlock(); -#endif return addr; } @@ -134,12 +121,6 @@ class HandleAllocator { D* addr = handle_cast(const_cast&>(handle)); assert_invariant(addr); new(addr) D(std::forward(args)...); - -#if HANDLE_TYPE_SAFETY - mLock.lock(); - mHandleTypeId[addr] = typeid(D).name(); - mLock.unlock(); -#endif return addr; } @@ -155,19 +136,8 @@ class HandleAllocator { void deallocate(Handle& handle, D const* p) noexcept { // allow to destroy the nullptr, similarly to operator delete if (p) { -#if HANDLE_TYPE_SAFETY - mLock.lock(); - auto typeId = mHandleTypeId[p]; - mHandleTypeId.erase(p); - mLock.unlock(); - if (UTILS_UNLIKELY(typeId != typeid(D).name())) { - utils::slog.e << "Destroying handle " << handle.getId() << ", type " << typeid(D).name() - << ", but handle's actual type is " << typeId << utils::io::endl; - std::terminate(); - } -#endif p->~D(); - deallocateHandle(handle.getId()); + deallocateHandle(handle.getId()); } } @@ -193,9 +163,21 @@ class HandleAllocator { inline typename std::enable_if_t< std::is_pointer_v && std::is_base_of_v>, Dp> - handle_cast(Handle& handle) noexcept { + handle_cast(Handle& handle) { assert_invariant(handle); - void* const p = handleToPointer(handle.getId()); + auto [p, tag] = handleToPointer(handle.getId()); + + if (isPoolHandle(handle.getId())) { + // check for use after free + 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); } @@ -203,71 +185,97 @@ class HandleAllocator { inline typename std::enable_if_t< std::is_pointer_v && std::is_base_of_v>, Dp> - handle_cast(Handle const& handle) noexcept { + handle_cast(Handle const& handle) { return handle_cast(const_cast&>(handle)); } - private: - // template + template + static constexpr size_t getBucketSize() noexcept { + if constexpr (sizeof(D) <= P0) { return P0; } + if constexpr (sizeof(D) <= P1) { return P1; } + static_assert(sizeof(D) <= P2); + return P2; + } + class Allocator { friend class HandleAllocator; - utils::PoolAllocator mPool0; - utils::PoolAllocator mPool1; - utils::PoolAllocator mPool2; + static constexpr size_t MIN_ALIGNMENT = alignof(std::max_align_t); + struct Node { uint8_t age; }; + // Note: using the `extra` parameter of PoolAllocator<>, even with a 1-byte structure, + // generally increases all pool allocations by 8-bytes because of alignment restrictions. + template + using Pool = utils::PoolAllocator; + Pool mPool0; + Pool mPool1; + Pool mPool2; UTILS_UNUSED_IN_RELEASE const utils::AreaPolicy::HeapArea& mArea; + bool mUseAfterFreeCheckDisabled; public: - static constexpr size_t MIN_ALIGNMENT_SHIFT = 4; - 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; } // this is in fact always called with a constexpr size argument - [[nodiscard]] inline void* alloc(size_t size, size_t alignment, size_t extra) noexcept { + [[nodiscard]] inline void* alloc(size_t size, size_t, size_t, uint8_t* outAge) noexcept { void* p = nullptr; - if (size <= mPool0.getSize()) p = mPool0.alloc(size, 16, extra); - else if (size <= mPool1.getSize()) p = mPool1.alloc(size, 16, extra); - else if (size <= mPool2.getSize()) p = mPool2.alloc(size, 16, extra); + if (size <= mPool0.getSize()) p = mPool0.alloc(size); + else if (size <= mPool1.getSize()) p = mPool1.alloc(size); + else if (size <= mPool2.getSize()) p = mPool2.alloc(size); + if (UTILS_LIKELY(p)) { + Node const* const pNode = static_cast(p); + // we are guaranteed to have at least sizeof bytes of extra storage before + // the allocation address. + *outAge = pNode[-1].age; + } return p; } // this is in fact always called with a constexpr size argument - inline void free(void* p, size_t size) noexcept { + inline void free(void* p, size_t size, uint8_t age) noexcept { assert_invariant(p >= mArea.begin() && (char*)p + size <= (char*)mArea.end()); + + // check for double-free + Node* const pNode = static_cast(p); + uint8_t& expectedAge = pNode[-1].age; + 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; } if (size <= mPool1.getSize()) { mPool1.free(p); return; } if (size <= mPool2.getSize()) { mPool2.free(p); return; } } }; - +// FIXME: We should be using a Spinlock here, at least on platforms where mutexes are not +// efficient (i.e. non-Linux). However, we've seen some hangs on that spinlock, which +// we don't understand well (b/308029108). #ifndef NDEBUG using HandleArena = utils::Arena; #else using HandleArena = utils::Arena; + utils::LockingPolicy::Mutex>; #endif // allocateHandle()/deallocateHandle() selects the pool to use at compile-time based on the // allocation size this is always inlined, because all these do is to call // allocateHandleInPool()/deallocateHandleFromPool() with the right pool size. - template + template HandleBase::HandleId allocateHandle() noexcept { - if constexpr (SIZE <= P0) { return allocateHandleInPool(); } - if constexpr (SIZE <= P1) { return allocateHandleInPool(); } - return allocateHandleInPool(); + constexpr size_t BUCKET_SIZE = getBucketSize(); + return allocateHandleInPool(); } - template + template void deallocateHandle(HandleBase::HandleId id) noexcept { - if constexpr (SIZE <= P0) { - deallocateHandleFromPool(id); - } else if constexpr (SIZE <= P1) { - deallocateHandleFromPool(id); - } else { - deallocateHandleFromPool(id); - } + constexpr size_t BUCKET_SIZE = getBucketSize(); + deallocateHandleFromPool(id); } // allocateHandleInPool()/deallocateHandleFromPool() is NOT inlined, which will cause three @@ -276,9 +284,11 @@ class HandleAllocator { template UTILS_NOINLINE HandleBase::HandleId allocateHandleInPool() noexcept { - void* p = mHandleArena.alloc(SIZE); + uint8_t age; + void* p = mHandleArena.alloc(SIZE, alignof(std::max_align_t), 0, &age); if (UTILS_LIKELY(p)) { - return pointerToHandle(p); + uint32_t const tag = (uint32_t(age) << HANDLE_AGE_SHIFT) & HANDLE_AGE_MASK; + return arenaPointerToHandle(p, tag); } else { return allocateHandleSlow(SIZE); } @@ -288,42 +298,51 @@ class HandleAllocator { UTILS_NOINLINE void deallocateHandleFromPool(HandleBase::HandleId id) noexcept { if (UTILS_LIKELY(isPoolHandle(id))) { - void* p = handleToPointer(id); - mHandleArena.free(p, SIZE); + auto [p, tag] = handleToPointer(id); + uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT; + mHandleArena.free(p, SIZE, age); } else { deallocateHandleSlow(id, SIZE); } } - static constexpr uint32_t HEAP_HANDLE_FLAG = 0x80000000u; + // we handle a 4 bits age per address + static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; // pool vs heap handle + static constexpr uint32_t HANDLE_AGE_MASK = 0x78000000u; // handle's age + static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; // handle index + static constexpr uint32_t HANDLE_TAG_MASK = HANDLE_AGE_MASK; + static constexpr uint32_t HANDLE_AGE_SHIFT = 27; static bool isPoolHandle(HandleBase::HandleId id) noexcept { - return (id & HEAP_HANDLE_FLAG) == 0u; + return (id & HANDLE_HEAP_FLAG) == 0u; } - HandleBase::HandleId allocateHandleSlow(size_t size) noexcept; + HandleBase::HandleId allocateHandleSlow(size_t size); void deallocateHandleSlow(HandleBase::HandleId id, size_t size) noexcept; // We inline this because it's just 4 instructions in the fast case - inline void* handleToPointer(HandleBase::HandleId id) const noexcept { + inline std::pair handleToPointer(HandleBase::HandleId id) const noexcept { // note: the null handle will end-up returning nullptr b/c it'll be handled as // a non-pool handle. if (UTILS_LIKELY(isPoolHandle(id))) { char* const base = (char*)mHandleArena.getArea().begin(); - size_t offset = id << Allocator::MIN_ALIGNMENT_SHIFT; - return static_cast(base + offset); + uint32_t const tag = id & HANDLE_TAG_MASK; + size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment(); + return { static_cast(base + offset), tag }; } - return handleToPointerSlow(id); + return { handleToPointerSlow(id), 0 }; } void* handleToPointerSlow(HandleBase::HandleId id) const noexcept; // We inline this because it's just 3 instructions - inline HandleBase::HandleId pointerToHandle(void* p) const noexcept { + inline HandleBase::HandleId arenaPointerToHandle(void* p, uint32_t tag) const noexcept { char* const base = (char*)mHandleArena.getArea().begin(); - size_t offset = (char*)p - base; - auto id = HandleBase::HandleId(offset >> Allocator::MIN_ALIGNMENT_SHIFT); - assert_invariant((id & HEAP_HANDLE_FLAG) == 0); + size_t const offset = (char*)p - base; + assert_invariant((offset % Allocator::getAlignment()) == 0); + auto id = HandleBase::HandleId(offset / Allocator::getAlignment()); + id |= tag & HANDLE_TAG_MASK; + assert_invariant((id & HANDLE_HEAP_FLAG) == 0); return id; } @@ -333,9 +352,7 @@ class HandleAllocator { mutable utils::Mutex mLock; tsl::robin_map mOverflowMap; HandleBase::HandleId mId = 0; -#if HANDLE_TYPE_SAFETY - mutable std::unordered_map mHandleTypeId; -#endif + bool mUseAfterFreeCheckDisabled = false; }; } // namespace filament::backend diff --git a/filament/backend/include/private/backend/PlatformFactory.h b/filament/backend/include/private/backend/PlatformFactory.h index fcdfc3c50cd..bf75bfb7e24 100644 --- a/filament/backend/include/private/backend/PlatformFactory.h +++ b/filament/backend/include/private/backend/PlatformFactory.h @@ -19,9 +19,9 @@ #ifndef TNT_FILAMENT_BACKEND_PLATFORM_FACTORY_H #define TNT_FILAMENT_BACKEND_PLATFORM_FACTORY_H -#include "backend/DriverEnums.h" +#include -#include "utils/compiler.h" +#include namespace filament::backend { diff --git a/filament/backend/include/private/backend/SamplerGroup.h b/filament/backend/include/private/backend/SamplerGroup.h index 855dcd119cb..187bd62c87d 100644 --- a/filament/backend/include/private/backend/SamplerGroup.h +++ b/filament/backend/include/private/backend/SamplerGroup.h @@ -17,15 +17,13 @@ #ifndef TNT_FILAMENT_BACKEND_PRIVATE_SAMPLERGROUP_H #define TNT_FILAMENT_BACKEND_PRIVATE_SAMPLERGROUP_H -#include "backend/DriverApiForward.h" - -#include -#include - +#include #include -#include #include +#include +#include + #include namespace filament::backend { diff --git a/filament/backend/src/CircularBuffer.cpp b/filament/backend/src/CircularBuffer.cpp index d9a877d3f59..41dd4173008 100644 --- a/filament/backend/src/CircularBuffer.cpp +++ b/filament/backend/src/CircularBuffer.cpp @@ -16,6 +16,14 @@ #include "private/backend/CircularBuffer.h" +#include +#include +#include +#include +#include +#include +#include + #if !defined(WIN32) && !defined(__EMSCRIPTEN__) && !defined(IOS) # include # include @@ -24,23 +32,20 @@ # define HAS_MMAP 0 #endif +#include +#include +#include #include -#include -#include -#include -#include -#include - using namespace utils; namespace filament::backend { size_t CircularBuffer::sPageSize = arch::getPageSize(); -CircularBuffer::CircularBuffer(size_t size) { +CircularBuffer::CircularBuffer(size_t size) + : mSize(size) { mData = alloc(size); - mSize = size; mTail = mData; mHead = mData; } @@ -85,7 +90,7 @@ void* CircularBuffer::alloc(size_t size) noexcept { MAP_PRIVATE, fd, (off_t)size); if (vaddr_guard != MAP_FAILED && (vaddr_guard == (char*)vaddr_shadow + size)) { // woo-hoo success! - mUsesAshmem = fd; + mAshmemFd = fd; data = vaddr; } } @@ -93,7 +98,7 @@ void* CircularBuffer::alloc(size_t size) noexcept { } } - if (UTILS_UNLIKELY(mUsesAshmem < 0)) { + if (UTILS_UNLIKELY(mAshmemFd < 0)) { // ashmem failed if (vaddr_guard != MAP_FAILED) { munmap(vaddr_guard, size); @@ -137,9 +142,9 @@ void CircularBuffer::dealloc() noexcept { if (mData) { size_t const BLOCK_SIZE = getBlockSize(); munmap(mData, mSize * 2 + BLOCK_SIZE); - if (mUsesAshmem >= 0) { - close(mUsesAshmem); - mUsesAshmem = -1; + if (mAshmemFd >= 0) { + close(mAshmemFd); + mAshmemFd = -1; } } #else @@ -149,23 +154,37 @@ void CircularBuffer::dealloc() noexcept { } -void CircularBuffer::circularize() noexcept { - if (mUsesAshmem > 0) { - intptr_t const overflow = intptr_t(mHead) - (intptr_t(mData) + ssize_t(mSize)); - if (overflow >= 0) { - assert_invariant(size_t(overflow) <= mSize); - mHead = (void *) (intptr_t(mData) + overflow); - #ifndef NDEBUG - memset(mData, 0xA5, size_t(overflow)); - #endif - } - } else { - // Only circularize if mHead if in the second buffer. - if (intptr_t(mHead) - intptr_t(mData) > ssize_t(mSize)) { +CircularBuffer::Range CircularBuffer::getBuffer() noexcept { + Range const range{ .tail = mTail, .head = mHead }; + + char* const pData = static_cast(mData); + char const* const pEnd = pData + mSize; + char const* const pHead = static_cast(mHead); + if (UTILS_UNLIKELY(pHead >= pEnd)) { + size_t const overflow = pHead - pEnd; + if (UTILS_LIKELY(mAshmemFd > 0)) { + assert_invariant(overflow <= mSize); + mHead = static_cast(pData + overflow); + // Data Tail End Head [virtual] + // v v v v + // +-------------:----+-----:--------------+ + // | : | : | + // +-----:------------+--------------------+ + // Head |<------ copy ------>| [physical] + } else { + // Data Tail End Head + // v v v v + // +-------------:----+-----:--------------+ + // | : | : | + // +-----|------------+-----|--------------+ + // |<---------------->| + // sliding window mHead = mData; } } mTail = mHead; + + return range; } } // namespace filament::backend diff --git a/filament/backend/src/CommandBufferQueue.cpp b/filament/backend/src/CommandBufferQueue.cpp index ccf9d33a0d7..b721ce0c50f 100644 --- a/filament/backend/src/CommandBufferQueue.cpp +++ b/filament/backend/src/CommandBufferQueue.cpp @@ -15,23 +15,35 @@ */ #include "private/backend/CommandBufferQueue.h" +#include "private/backend/CircularBuffer.h" +#include "private/backend/CommandStream.h" +#include #include -#include +#include +#include #include +#include #include -#include "private/backend/BackendUtils.h" -#include "private/backend/CommandStream.h" +#include +#include +#include +#include +#include + +#include +#include 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); } @@ -45,6 +57,16 @@ void CommandBufferQueue::requestExit() { mCondition.notify_one(); } +void CommandBufferQueue::setPaused(bool paused) { + std::lock_guard const lock(mLock); + 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, @@ -65,55 +87,61 @@ void CommandBufferQueue::flush() noexcept { // always guaranteed to have enough space for the NoopCommand new(circularBuffer.allocate(sizeof(NoopCommand))) NoopCommand(nullptr); - // end of this slice - void* const head = circularBuffer.getHead(); + const size_t requiredSize = mRequiredSize; - // beginning of this slice - void* const tail = circularBuffer.getTail(); + // get the current buffer + auto const [begin, end] = circularBuffer.getBuffer(); - // size of this slice - uint32_t const used = uint32_t(intptr_t(head) - intptr_t(tail)); + assert_invariant(circularBuffer.empty()); - circularBuffer.circularize(); + // size of the current buffer + size_t const used = std::distance( + static_cast(begin), static_cast(end)); std::unique_lock lock(mLock); - mCommandBuffersToExecute.push_back({ tail, head }); + mCommandBuffersToExecute.push_back({ begin, end }); + mCondition.notify_one(); // circular buffer is too small, we corrupted the stream ASSERT_POSTCONDITION(used <= mFreeSpace, "Backend CommandStream overflow. Commands are corrupted and unrecoverable.\n" "Please increase minCommandBufferSizeMB inside the Config passed to Engine::create.\n" - "Space used at this time: %u bytes", - (unsigned)used); + "Space used at this time: %u bytes, overflow: %u bytes", + (unsigned)used, unsigned(used - mFreeSpace)); // wait until there is enough space in the buffer mFreeSpace -= used; - const size_t requiredSize = mRequiredSize; + if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) { + #ifndef NDEBUG - size_t totalUsed = circularBuffer.size() - mFreeSpace; - mHighWatermark = std::max(mHighWatermark, totalUsed); - if (UTILS_UNLIKELY(totalUsed > requiredSize)) { - slog.d << "CommandStream used too much space: " << totalUsed - << ", out of " << requiredSize << " (will block)" << io::endl; - } + size_t const totalUsed = circularBuffer.size() - mFreeSpace; + slog.d << "CommandStream used too much space (will block): " + << "needed space " << requiredSize << " out of " << mFreeSpace + << ", totalUsed=" << totalUsed << ", current=" << used + << ", queue size=" << mCommandBuffersToExecute.size() << " buffers" + << io::endl; + + mHighWatermark = std::max(mHighWatermark, totalUsed); #endif - mCondition.notify_one(); - if (UTILS_LIKELY(mFreeSpace < requiredSize)) { SYSTRACE_NAME("waiting: CircularBuffer::flush()"); + ASSERT_POSTCONDITION(!mPaused, + "CommandStream is full, but since the rendering thread is paused, " + "the buffer cannot flush and we will deadlock. Instead, abort."); mCondition.wait(lock, [this, requiredSize]() -> bool { + // TODO: on macOS, we need to call pumpEvents from time to time return mFreeSpace >= requiredSize; }); } } -std::vector CommandBufferQueue::waitForCommands() const { +std::vector CommandBufferQueue::waitForCommands() const { if (!UTILS_HAS_THREADING) { return std::move(mCommandBuffersToExecute); } std::unique_lock lock(mLock); - while (mCommandBuffersToExecute.empty() && !mExitRequested) { + while ((mCommandBuffersToExecute.empty() || mPaused) && !mExitRequested) { mCondition.wait(lock); } @@ -123,7 +151,7 @@ std::vector CommandBufferQueue::waitForCommands() con return std::move(mCommandBuffersToExecute); } -void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Slice const& buffer) { +void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) { std::lock_guard const lock(mLock); mFreeSpace += uintptr_t(buffer.end) - uintptr_t(buffer.begin); mCondition.notify_one(); diff --git a/filament/backend/src/CommandStream.cpp b/filament/backend/src/CommandStream.cpp index 29bb2184575..69730d688c8 100644 --- a/filament/backend/src/CommandStream.cpp +++ b/filament/backend/src/CommandStream.cpp @@ -149,7 +149,7 @@ void CommandType::Command::log() noexcept { // ------------------------------------------------------------------------------------------------ -void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) noexcept { +void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) { *next = CustomCommand::align(sizeof(CustomCommand)); static_cast(base)->mCommand(); static_cast(base)->~CustomCommand(); diff --git a/filament/backend/src/DataReshaper.h b/filament/backend/src/DataReshaper.h index 4815113326e..eebe6b37d17 100644 --- a/filament/backend/src/DataReshaper.h +++ b/filament/backend/src/DataReshaper.h @@ -19,6 +19,7 @@ #include +#include #include #include diff --git a/filament/backend/src/Driver.cpp b/filament/backend/src/Driver.cpp index b3bbab4a6ca..f33f855d9f4 100644 --- a/filament/backend/src/Driver.cpp +++ b/filament/backend/src/Driver.cpp @@ -21,7 +21,12 @@ #include #include +#include +#include +#include +#include +#include #include #include @@ -29,6 +34,13 @@ #include #include +#include +#include +#include + +#include +#include + using namespace utils; using namespace filament::math; @@ -119,7 +131,8 @@ void DriverBase::purge() noexcept { // ------------------------------------------------------------------------------------------------ void DriverBase::scheduleDestroySlow(BufferDescriptor&& buffer) noexcept { - scheduleCallback(buffer.getHandler(), [buffer = std::move(buffer)]() { + auto const handler = buffer.getHandler(); + scheduleCallback(handler, [buffer = std::move(buffer)]() { // user callback is called when BufferDescriptor gets destroyed }); } @@ -201,7 +214,7 @@ size_t Driver::getElementTypeSize(ElementType type) noexcept { Driver::~Driver() noexcept = default; -void Driver::execute(std::function const& fn) noexcept { +void Driver::execute(std::function const& fn) { fn(); } diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index abf68901164..24acfe60e1b 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -49,21 +49,25 @@ struct AcquiredImage; struct HwBase { }; -struct HwVertexBuffer : public HwBase { - AttributeArray attributes{}; // 8 * MAX_VERTEX_ATTRIBUTE_COUNT - uint32_t vertexCount{}; // 4 + +struct HwVertexBufferInfo : public HwBase { uint8_t bufferCount{}; // 1 uint8_t attributeCount{}; // 1 - bool padding{}; // 1 - uint8_t bufferObjectsVersion{}; // 1 -> total struct is 136 bytes + bool padding[2]{}; // 2 + HwVertexBufferInfo() noexcept = default; + HwVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount) noexcept + : bufferCount(bufferCount), + attributeCount(attributeCount) { + } +}; +struct HwVertexBuffer : public HwBase { + uint32_t vertexCount{}; // 4 + uint8_t bufferObjectsVersion{0xff}; // 1 + bool padding[3]{}; // 2 HwVertexBuffer() noexcept = default; - HwVertexBuffer(uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount, - AttributeArray const& attributes) noexcept - : attributes(attributes), - vertexCount(elementCount), - bufferCount(bufferCount), - attributeCount(attributeCount) { + explicit HwVertexBuffer(uint32_t vertextCount) noexcept + : vertexCount(vertextCount) { } }; @@ -88,11 +92,6 @@ struct HwIndexBuffer : public HwBase { }; struct HwRenderPrimitive : public HwBase { - uint32_t offset{}; - uint32_t minIndex{}; - uint32_t maxIndex{}; - uint32_t count{}; - uint32_t maxVertexCount{}; PrimitiveType type = PrimitiveType::TRIANGLES; }; @@ -114,7 +113,9 @@ struct HwTexture : public HwBase { uint8_t levels : 4; // This allows up to 15 levels (max texture size of 32768 x 32768) uint8_t samples : 4; // Sample count per pixel (should always be a power of 2) TextureFormat format{}; + uint8_t reserved0 = 0; TextureUsage usage{}; + uint16_t reserved1 = 0; HwStream* hwStream = nullptr; HwTexture() noexcept : levels{}, samples{} {} diff --git a/filament/backend/src/HandleAllocator.cpp b/filament/backend/src/HandleAllocator.cpp index 3257e4e2c94..a5a9e7c98c8 100644 --- a/filament/backend/src/HandleAllocator.cpp +++ b/filament/backend/src/HandleAllocator.cpp @@ -16,9 +16,22 @@ #include "private/backend/HandleAllocator.h" +#include + +#include +#include #include +#include +#include +#include + +#include +#include +#include +#include #include +#include namespace filament::backend { @@ -26,23 +39,47 @@ using namespace utils; template UTILS_NOINLINE -HandleAllocator::Allocator::Allocator(AreaPolicy::HeapArea const& area) - : mArea(area) { - // TODO: we probably need a better way to set the size of these pools - const size_t unit = area.size() / 32; - const size_t offsetPool1 = unit; - const size_t offsetPool2 = 16 * unit; - char* const p = (char*)area.begin(); - mPool0 = PoolAllocator< P0, 16>(p, p + offsetPool1); - mPool1 = PoolAllocator< P1, 16>(p + offsetPool1, p + offsetPool2); - mPool2 = PoolAllocator< P2, 16>(p + offsetPool2, area.end()); +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. + // e.g. On Android armv8, the alignment is 16 bytes, so for a 1 MiB heap, the largest handle + // index will be 65536. Note that this is not the same as the number of handles (which + // will always be less). + // Because our maximum representable handle currently is 0x07FFFFFF, the maximum no-nonsensical + // heap size is 2 GiB, which amounts to 7.6 millions handles per pool (in the GL case). + size_t const maxHeapSize = std::min(area.size(), HANDLE_INDEX_MASK * getAlignment()); + + if (UTILS_UNLIKELY(maxHeapSize != area.size())) { + slog.w << "HandleAllocator heap size reduced to " + << maxHeapSize << " from " << area.size() << io::endl; + } + + // make sure we start with a clean arena. This is needed to ensure that all blocks start + // with an age of 0. + memset(area.data(), 0, maxHeapSize); + + // size the different pools so that they can all contain the same number of handles + size_t const count = maxHeapSize / (P0 + P1 + P2); + char* const p0 = static_cast(area.begin()); + char* const p1 = p0 + count * P0; + char* const p2 = p1 + count * P1; + + mPool0 = Pool(p0, count * P0); + mPool1 = Pool(p1, count * P1); + mPool2 = Pool(p2, count * P2); } // ------------------------------------------------------------------------------------------------ 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 @@ -70,14 +107,20 @@ void* HandleAllocator::handleToPointerSlow(HandleBase::HandleId id) } template -HandleBase::HandleId HandleAllocator::allocateHandleSlow(size_t size) noexcept { +HandleBase::HandleId HandleAllocator::allocateHandleSlow(size_t size) { void* p = ::malloc(size); std::unique_lock lock(mLock); - HandleBase::HandleId id = (++mId) | HEAP_HANDLE_FLAG; + + HandleBase::HandleId id = (++mId) | HANDLE_HEAP_FLAG; + + ASSERT_POSTCONDITION(mId < HANDLE_HEAP_FLAG, + "No more Handle ids available! This can happen if HandleAllocator arena has been full" + " for a while. Please increase FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB"); + mOverflowMap.emplace(id, p); lock.unlock(); - if (UTILS_UNLIKELY(id == (HEAP_HANDLE_FLAG|1u))) { // meaning id was zero + if (UTILS_UNLIKELY(id == (HANDLE_HEAP_FLAG | 1u))) { // meaning id was zero PANIC_LOG("HandleAllocator arena is full, using slower system heap. Please increase " "the appropriate constant (e.g. FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB)."); } @@ -86,7 +129,7 @@ HandleBase::HandleId HandleAllocator::allocateHandleSlow(size_t size template void HandleAllocator::deallocateHandleSlow(HandleBase::HandleId id, size_t) noexcept { - assert_invariant(id & HEAP_HANDLE_FLAG); + assert_invariant(id & HANDLE_HEAP_FLAG); void* p = nullptr; auto& overflowMap = mOverflowMap; diff --git a/filament/backend/src/Platform.cpp b/filament/backend/src/Platform.cpp index ece36477bab..77c85129417 100644 --- a/filament/backend/src/Platform.cpp +++ b/filament/backend/src/Platform.cpp @@ -28,14 +28,16 @@ bool Platform::pumpEvents() noexcept { } void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept { - if (!mInsertBlob && !mRetrieveBlob) { - mInsertBlob = std::move(insertBlob); - mRetrieveBlob = std::move(retrieveBlob); - } + mInsertBlob = std::move(insertBlob); + mRetrieveBlob = std::move(retrieveBlob); +} + +bool Platform::hasInsertBlobFunc() const noexcept { + return bool(mInsertBlob); } -bool Platform::hasBlobFunc() const noexcept { - return mInsertBlob && mRetrieveBlob; +bool Platform::hasRetrieveBlobFunc() const noexcept { + return bool(mRetrieveBlob); } void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) { @@ -51,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/Program.cpp b/filament/backend/src/Program.cpp index 1bae22d97d0..dc92e8c2a26 100644 --- a/filament/backend/src/Program.cpp +++ b/filament/backend/src/Program.cpp @@ -91,6 +91,11 @@ Program& Program::cacheId(uint64_t cacheId) noexcept { return *this; } +Program& Program::multiview(bool multiview) noexcept { + mMultiview = multiview; + return *this; +} + io::ostream& operator<<(io::ostream& out, const Program& builder) { out << "Program{"; builder.mLogger(out); diff --git a/filament/backend/src/metal/MetalBlitter.h b/filament/backend/src/metal/MetalBlitter.h index 393b84d0bcc..3882c18df3b 100644 --- a/filament/backend/src/metal/MetalBlitter.h +++ b/filament/backend/src/metal/MetalBlitter.h @@ -24,8 +24,7 @@ #include #include -namespace filament { -namespace backend { +namespace filament::backend { struct MetalContext; @@ -37,8 +36,7 @@ class MetalBlitter { struct BlitArgs { struct Attachment { - id color = nil; - id depth = nil; + id texture = nil; MTLRegion region = {}; uint8_t level = 0; uint32_t slice = 0; // must be 0 on source attachment @@ -46,25 +44,13 @@ class MetalBlitter { // Valid source formats: 2D, 2DArray, 2DMultisample, 3D // Valid destination formats: 2D, 2DArray, 3D, Cube - Attachment source, destination; + Attachment source; + Attachment destination; SamplerMagFilter filter; - bool blitColor() const { - return source.color != nil && destination.color != nil; - } - - bool blitDepth() const { - return source.depth != nil && destination.depth != nil; - } - - bool colorDestinationIsFullAttachment() const { - return destination.color.width == destination.region.size.width && - destination.color.height == destination.region.size.height; - } - - bool depthDestinationIsFullAttachment() const { - return destination.depth.width == destination.region.size.width && - destination.depth.height == destination.region.size.height; + bool destinationIsFullAttachment() const { + return destination.texture.width == destination.region.size.width && + destination.texture.height == destination.region.size.height; } }; @@ -78,48 +64,36 @@ class MetalBlitter { private: - static void setupColorAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor, - uint32_t depthPlane); - static void setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor, - uint32_t depthPlane); + static void setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor, + const BlitArgs& args, uint32_t depthPlane); struct BlitFunctionKey { - bool blitColor; - bool blitDepth; - bool msaaColorSource; - bool msaaDepthSource; - bool sources3D; - - char padding[3]; + bool msaaColorSource{}; + bool sources3D{}; + char padding[2]{}; bool isValid() const noexcept { // MSAA 3D textures do not exist. - bool hasMsaa = msaaColorSource || msaaDepthSource; + bool const hasMsaa = msaaColorSource; return !(hasMsaa && sources3D); } bool operator==(const BlitFunctionKey& rhs) const noexcept { - return blitColor == rhs.blitColor && - blitDepth == rhs.blitDepth && - msaaColorSource == rhs.msaaColorSource && - msaaDepthSource == rhs.msaaDepthSource && + return msaaColorSource == rhs.msaaColorSource && sources3D == rhs.sources3D; } - - BlitFunctionKey() { - std::memset(this, 0, sizeof(BlitFunctionKey)); - } }; - void blitFastPath(id cmdBuffer, bool& blitColor, bool& blitDepth, + static bool blitFastPath(id cmdBuffer, const BlitArgs& args, const char* label); - void blitSlowPath(id cmdBuffer, bool& blitColor, bool& blitDepth, + + void blitSlowPath(id cmdBuffer, const BlitArgs& args, const char* label); - void blitDepthPlane(id cmdBuffer, bool blitColor, bool blitDepth, - const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest, - const char* label); - id createIntermediateTexture(id t, MTLSize size); - id compileFragmentFunction(BlitFunctionKey key); + + void blitDepthPlane(id cmdBuffer, const BlitArgs& args, + uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label); + + id compileFragmentFunction(BlitFunctionKey key) const; id getBlitVertexFunction(); id getBlitFragmentFunction(BlitFunctionKey key); @@ -130,10 +104,9 @@ class MetalBlitter { tsl::robin_map mBlitFunctions; id mVertexFunction = nil; - }; -} // namespace backend -} // namespace filament +} // namespace filament::backend + #endif //TNT_METALBLITTER_H diff --git a/filament/backend/src/metal/MetalBlitter.mm b/filament/backend/src/metal/MetalBlitter.mm index d5b856e82b8..51e1fc3b88d 100644 --- a/filament/backend/src/metal/MetalBlitter.mm +++ b/filament/backend/src/metal/MetalBlitter.mm @@ -20,9 +20,9 @@ #include "MetalUtils.h" #include +#include -namespace filament { -namespace backend { +namespace filament::backend { static const char* functionLibrary = R"( #include @@ -37,13 +37,7 @@ struct FragmentOut { -#ifdef BLIT_COLOR float4 color [[color(0)]]; -#endif - -#ifdef BLIT_DEPTH - float depth [[depth(any)]]; -#endif }; vertex VertexOut @@ -67,7 +61,6 @@ blitterFrag(VertexOut in [[stage_in]], sampler sourceSampler [[sampler(0)]], -#ifdef BLIT_COLOR #ifdef MSAA_COLOR_SOURCE texture2d_ms sourceColor [[texture(0)]], #elif SOURCES_3D @@ -75,32 +68,18 @@ #else texture2d sourceColor [[texture(0)]], #endif // MSAA_COLOR_SOURCE -#endif // BLIT_COLOR - -#ifdef BLIT_DEPTH -#ifdef MSAA_DEPTH_SOURCE - texture2d_ms sourceDepth [[texture(1)]], -#elif SOURCES_3D - texture3d sourceDepth [[texture(1)]], -#else - texture2d sourceDepth [[texture(1)]], -#endif // MSAA_DEPTH_SOURCE -#endif // BLIT_DEPTH constant FragmentArgs* args [[buffer(0)]]) { FragmentOut out = {}; -#if defined(BLIT_COLOR) || defined(BLIT_DEPTH) // These coordinates match the Vulkan vkCmdBlitImage spec: // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdBlitImage.html float2 uvbase = in.position.xy; // unnormalized coordinates at center of texel: (1.5, 2.5, etc) float2 uvoffset = uvbase - args->dstOffset; float2 uvscaled = uvoffset * args->scale; float2 uv = uvscaled + args->srcOffset; -#endif -#ifdef BLIT_COLOR #ifdef MSAA_COLOR_SOURCE out.color = float4(0.0); for (uint s = 0; s < sourceColor.get_num_samples(); s++) { @@ -116,201 +95,106 @@ float2 uvnorm = uv / float2(sourceColor.get_width(args->lod), sourceColor.get_height(args->lod)); out.color += sourceColor.sample(sourceSampler, uvnorm, level(args->lod)); #endif // MSAA_COLOR_SOURCE -#endif // BLIT_COLOR -#ifdef BLIT_DEPTH -#ifdef MSAA_DEPTH_SOURCE - out.depth = 0.0; - for (uint s = 0; s < sourceDepth.get_num_samples(); s++) { - out.depth += sourceDepth.read(static_cast(uv), s).r; - } - out.depth /= sourceDepth.get_num_samples(); -#elif SOURCES_3D - float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod)); - float3 coords = float3(uvnormd, (static_cast(args->depthPlane) + 0.5) / - sourceDepth.get_depth(args->lod)); - out.depth = sourceDepth.sample(sourceSampler, coords, level(args->lod)).r; -#else - float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod)); - out.depth = sourceDepth.sample(sourceSampler, uvnormd, level(args->lod)).r; -#endif // MSAA_DEPTH_SOURCE -#endif // BLIT_DEPTH return out; } )"; -MetalBlitter::MetalBlitter(MetalContext& context) noexcept : mContext(context) { } -#define MTLSizeEqual(a, b) (a.width == b.width && a.height == b.height && a.depth == b.depth) +template +inline bool MTLSizeEqual(T a, T b) noexcept { + return (a.width == b.width && a.height == b.height && a.depth == b.depth); +} + +MetalBlitter::MetalBlitter(MetalContext& context) noexcept : mContext(context) { } void MetalBlitter::blit(id cmdBuffer, const BlitArgs& args, const char* label) { - bool blitColor = args.blitColor(); - bool blitDepth = args.blitDepth(); ASSERT_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth, "Blitting requires the source and destination regions to have the same depth."); - if (args.source.color && args.source.depth) { - MTLTextureType colorType = args.source.color.textureType; - MTLTextureType depthType = args.source.depth.textureType; - - if (colorType == MTLTextureType2DMultisample) colorType = MTLTextureType2D; - if (depthType == MTLTextureType2DMultisample) depthType = MTLTextureType2D; - - ASSERT_PRECONDITION(colorType == depthType, - "Blitting requires color and depth sources to be the same texture type."); - } - // Determine if the blit for color or depth are eligible to use a MTLBlitCommandEncoder. - // blitColor and / or blitDepth are set to false upon success, to indicate that no more work is - // necessary for that attachment. - blitFastPath(cmdBuffer, blitColor, blitDepth, args, label); - - if (!blitColor && !blitDepth) { + // blitFastPath returns true upon success. + // blitFastPath fails if either the format or sampleCount don't match (i.e. resolve) + if (blitFastPath(cmdBuffer, args, label)) { return; } - // If the destination is MSAA and we weren't able to use the fast path, report an error, as - // blitting to a MSAA texture isn't supported through the "slow path" yet. - const bool colorDestinationIsMultisample = - blitColor && args.destination.color.textureType == MTLTextureType2DMultisample; - const bool depthDestinationIsMultisample = - blitDepth && args.destination.depth.textureType == MTLTextureType2DMultisample; - ASSERT_PRECONDITION(!colorDestinationIsMultisample && !depthDestinationIsMultisample, - "Blitting between MSAA render targets with differing pixel formats and/or regions is not supported."); - - // If the destination texture doesn't have the MTLTextureUsageRenderTarget flag, we have to blit - // to an intermediate texture first to perform the format conversion. Then, we can perform a - // "fast blit" to the final destination texture. - - id intermediateColor = nil; - id intermediateDepth = nil; - BlitArgs slowBlit = args; - BlitArgs finalBlit = args; - - MTLRegion sourceRegionNoOffset = MTLRegionMake3D(0, 0, 0, - args.source.region.size.width, args.source.region.size.height, - args.source.region.size.depth); - - if (blitColor && !(args.destination.color.usage & MTLTextureUsageRenderTarget)) { - intermediateColor = createIntermediateTexture(args.destination.color, args.source.region.size); - slowBlit.destination.color = finalBlit.source.color = intermediateColor; - slowBlit.destination.level = finalBlit.source.level = 0; - slowBlit.destination.slice = finalBlit.source.slice = 0; - slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset; - } - if (blitDepth && !(args.destination.depth.usage & MTLTextureUsageRenderTarget)) { - intermediateDepth = createIntermediateTexture(args.destination.depth, args.source.region.size); - slowBlit.destination.depth = finalBlit.source.depth = intermediateDepth; - slowBlit.destination.level = finalBlit.source.level = 0; - slowBlit.destination.slice = finalBlit.source.slice = 0; - slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset; - } + // If we end-up here, it means that either: + // - we're resolving a color texture + // - src/dest formats didn't match, or we're scaling -- this can only happen with the legacy + // blit() path and implies that the format is not depth. + // note: in the future we will support a fast-path resolve - blitSlowPath(cmdBuffer, blitColor, blitDepth, slowBlit, label); + UTILS_UNUSED_IN_RELEASE + const bool destinationIsMultisample = + args.destination.texture.textureType == MTLTextureType2DMultisample; - bool finalBlitColor = intermediateColor != nil; - bool finalBlitDepth = intermediateDepth != nil; - blitFastPath(cmdBuffer, finalBlitColor, finalBlitDepth, finalBlit, label); + assert_invariant(!destinationIsMultisample); + assert_invariant((args.destination.texture.usage & MTLTextureUsageRenderTarget)); + blitSlowPath(cmdBuffer, args, label); } -void MetalBlitter::blitFastPath(id cmdBuffer, bool& blitColor, bool& blitDepth, +bool MetalBlitter::blitFastPath(id cmdBuffer, const BlitArgs& args, const char* label) { - if (blitColor) { - if (args.source.color.sampleCount == args.destination.color.sampleCount && - args.source.color.pixelFormat == args.destination.color.pixelFormat && - MTLSizeEqual(args.source.region.size, args.destination.region.size)) { - - id blitEncoder = [cmdBuffer blitCommandEncoder]; - blitEncoder.label = @(label); - [blitEncoder copyFromTexture:args.source.color - sourceSlice:args.source.slice - sourceLevel:args.source.level - sourceOrigin:args.source.region.origin - sourceSize:args.source.region.size - toTexture:args.destination.color - destinationSlice:args.destination.slice - destinationLevel:args.destination.level - destinationOrigin:args.destination.region.origin]; - [blitEncoder endEncoding]; - - blitColor = false; - } - } - if (blitDepth) { - if (args.source.depth.sampleCount == args.destination.depth.sampleCount && - args.source.depth.pixelFormat == args.destination.depth.pixelFormat && - MTLSizeEqual(args.source.region.size, args.destination.region.size)) { - - id blitEncoder = [cmdBuffer blitCommandEncoder]; - blitEncoder.label = @(label); - [blitEncoder copyFromTexture:args.source.depth - sourceSlice:args.source.slice - sourceLevel:args.source.level - sourceOrigin:args.source.region.origin - sourceSize:args.source.region.size - toTexture:args.destination.depth - destinationSlice:args.destination.slice - destinationLevel:args.destination.level - destinationOrigin:args.destination.region.origin]; - [blitEncoder endEncoding]; - - blitDepth = false; - } + if (args.source.texture.sampleCount == args.destination.texture.sampleCount && + args.source.texture.pixelFormat == args.destination.texture.pixelFormat && + MTLSizeEqual(args.source.region.size, args.destination.region.size)) { + + id const blitEncoder = [cmdBuffer blitCommandEncoder]; + blitEncoder.label = @(label); + [blitEncoder copyFromTexture:args.source.texture + sourceSlice:args.source.slice + sourceLevel:args.source.level + sourceOrigin:args.source.region.origin + sourceSize:args.source.region.size + toTexture:args.destination.texture + destinationSlice:args.destination.slice + destinationLevel:args.destination.level + destinationOrigin:args.destination.region.origin]; + [blitEncoder endEncoding]; + + return true; } + return false; } -void MetalBlitter::blitSlowPath(id cmdBuffer, bool& blitColor, bool& blitDepth, +void MetalBlitter::blitSlowPath(id cmdBuffer, const BlitArgs& args, const char* label) { - + // scaling in any dimension is not allowed + assert_invariant(args.source.region.size.depth == args.destination.region.size.depth); + // we're always blitting a single plane uint32_t depthPlaneSource = args.source.region.origin.z; uint32_t depthPlaneDest = args.destination.region.origin.z; - - assert_invariant(args.source.region.size.depth == args.destination.region.size.depth); - uint32_t depthPlaneCount = args.source.region.size.depth; - for (NSUInteger d = 0; d < depthPlaneCount; d++) { - blitDepthPlane(cmdBuffer, blitColor, blitDepth, args, depthPlaneSource++, depthPlaneDest++, - label); + for (NSUInteger d = 0; d < args.source.region.size.depth; d++) { + blitDepthPlane(cmdBuffer, args, + depthPlaneSource++, depthPlaneDest++, label); } - - blitColor = false; - blitDepth = false; } -void MetalBlitter::blitDepthPlane(id cmdBuffer, bool blitColor, bool blitDepth, - const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest, - const char* label) { - MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor]; +void MetalBlitter::blitDepthPlane(id cmdBuffer, const BlitArgs& args, + uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label) { - if (blitColor) { - setupColorAttachment(args, descriptor, depthPlaneDest); - } + MTLRenderPassDescriptor* const descriptor = [MTLRenderPassDescriptor renderPassDescriptor]; - if (blitDepth) { - setupDepthAttachment(args, descriptor, depthPlaneDest); - } + setupAttachment(descriptor.colorAttachments[0], args, depthPlaneDest); - id encoder = [cmdBuffer renderCommandEncoderWithDescriptor:descriptor]; + id const encoder = + [cmdBuffer renderCommandEncoderWithDescriptor:descriptor]; encoder.label = @(label); BlitFunctionKey key; - key.blitColor = blitColor; - key.blitDepth = blitDepth; - key.msaaColorSource = blitColor && args.source.color.textureType == MTLTextureType2DMultisample; - key.msaaDepthSource = blitDepth && args.source.depth.textureType == MTLTextureType2DMultisample; - key.sources3D = blitColor && args.source.color.textureType == MTLTextureType3D; - if (key.sources3D && blitDepth) { - assert_invariant(args.source.depth.textureType == MTLTextureType3D); - } - id fragmentFunction = getBlitFragmentFunction(key); + key.msaaColorSource = args.source.texture.textureType == MTLTextureType2DMultisample; + key.sources3D = args.source.texture.textureType == MTLTextureType3D; + id const fragmentFunction = getBlitFragmentFunction(key); - MetalPipelineState pipelineState { + MetalPipelineState const pipelineState { .vertexFunction = getBlitVertexFunction(), .fragmentFunction = fragmentFunction, .vertexDescription = {}, .colorAttachmentPixelFormat = { - blitColor ? args.destination.color.pixelFormat : MTLPixelFormatInvalid, + args.destination.texture.pixelFormat, MTLPixelFormatInvalid, MTLPixelFormatInvalid, MTLPixelFormatInvalid, @@ -319,31 +203,21 @@ MTLPixelFormatInvalid, MTLPixelFormatInvalid }, - .depthAttachmentPixelFormat = - blitDepth ? args.destination.depth.pixelFormat : MTLPixelFormatInvalid, + .depthAttachmentPixelFormat = MTLPixelFormatInvalid, .sampleCount = 1, .blendState = {} }; - id pipeline = mContext.pipelineStateCache.getOrCreateState(pipelineState); + id const pipeline = + mContext.pipelineStateCache.getOrCreateState(pipelineState); [encoder setRenderPipelineState:pipeline]; // For texture arrays, create a view of the texture at the given slice (layer). - id srcTextureColor = args.source.color; + + id srcTextureColor = args.source.texture; if (srcTextureColor && srcTextureColor.textureType == MTLTextureType2DArray) { srcTextureColor = createTextureViewWithSingleSlice(srcTextureColor, args.source.slice); } - id srcTextureDepth = args.source.depth; - if (srcTextureDepth && srcTextureDepth.textureType == MTLTextureType2DArray) { - srcTextureDepth = createTextureViewWithSingleSlice(srcTextureDepth, args.source.slice); - } - - if (blitColor) { - [encoder setFragmentTexture:srcTextureColor atIndex:0]; - } - - if (blitDepth) { - [encoder setFragmentTexture:srcTextureDepth atIndex:1]; - } + [encoder setFragmentTexture:srcTextureColor atIndex:0]; SamplerMinFilter filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST; if (args.filter == SamplerMagFilter::NEAREST) { @@ -352,13 +226,13 @@ filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; } - SamplerState s { + SamplerState const s { .samplerParams = { .filterMag = args.filter, .filterMin = filterMin } }; - id sampler = mContext.samplerStateCache.getOrCreateState(s); + id const sampler = mContext.samplerStateCache.getOrCreateState(s); [encoder setFragmentSamplerState:sampler atIndex:0]; MTLViewport viewport; @@ -370,11 +244,11 @@ viewport.zfar = 1.0; [encoder setViewport:viewport]; - DepthStencilState depthStencilState { + DepthStencilState const depthStencilState { .depthCompare = MTLCompareFunctionAlways, - .depthWriteEnabled = blitDepth + .depthWriteEnabled = false }; - id depthStencil = + id const depthStencil = mContext.depthStencilStateCache.getOrCreateState(depthStencilState); [encoder setDepthStencilState:depthStencil]; @@ -410,80 +284,42 @@ [encoder endEncoding]; } -id MetalBlitter::createIntermediateTexture(id t, MTLSize size) { - MTLTextureDescriptor* descriptor = [MTLTextureDescriptor new]; - descriptor.textureType = size.depth == 1 ? MTLTextureType2D : MTLTextureType3D; - descriptor.pixelFormat = t.pixelFormat; - descriptor.width = size.width; - descriptor.height = size.height; - descriptor.depth = size.depth; - descriptor.usage = t.usage & MTLTextureUsageRenderTarget; - return [mContext.device newTextureWithDescriptor:descriptor]; -} - void MetalBlitter::shutdown() noexcept { mBlitFunctions.clear(); mVertexFunction = nil; } -void MetalBlitter::setupColorAttachment(const BlitArgs& args, - MTLRenderPassDescriptor* descriptor, uint32_t depthPlane) { - descriptor.colorAttachments[0].texture = args.destination.color; - descriptor.colorAttachments[0].level = args.destination.level; - descriptor.colorAttachments[0].slice = args.destination.slice; - descriptor.colorAttachments[0].depthPlane = depthPlane; - - descriptor.colorAttachments[0].loadAction = MTLLoadActionLoad; +void MetalBlitter::setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor, + const BlitArgs& args, uint32_t depthPlane) { + descriptor.texture = args.destination.texture; + descriptor.level = args.destination.level; + descriptor.slice = args.destination.slice; + descriptor.depthPlane = depthPlane; + descriptor.loadAction = MTLLoadActionLoad; // We don't need to load the contents of the attachment if we're blitting over all of it. - if (args.colorDestinationIsFullAttachment()) { - descriptor.colorAttachments[0].loadAction = MTLLoadActionDontCare; + if (args.destinationIsFullAttachment()) { + descriptor.loadAction = MTLLoadActionDontCare; } - - descriptor.colorAttachments[0].storeAction = MTLStoreActionStore; -} - -void MetalBlitter::setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor, - uint32_t depthPlane) { - descriptor.depthAttachment.texture = args.destination.depth; - descriptor.depthAttachment.level = args.destination.level; - descriptor.depthAttachment.slice = args.destination.slice; - descriptor.depthAttachment.depthPlane = depthPlane; - - descriptor.depthAttachment.loadAction = MTLLoadActionLoad; - // We don't need to load the contents of the attachment if we're blitting over all of it. - if (args.depthDestinationIsFullAttachment()) { - descriptor.depthAttachment.loadAction = MTLLoadActionDontCare; - } - - descriptor.depthAttachment.storeAction = MTLStoreActionStore; + descriptor.storeAction = MTLStoreActionStore; } -id MetalBlitter::compileFragmentFunction(BlitFunctionKey key) { - MTLCompileOptions* options = [MTLCompileOptions new]; - NSMutableDictionary* macros = [NSMutableDictionary dictionary]; - if (key.blitColor) { - macros[@"BLIT_COLOR"] = @"1"; - } - if (key.blitDepth) { - macros[@"BLIT_DEPTH"] = @"1"; - } +id MetalBlitter::compileFragmentFunction(BlitFunctionKey key) const { + MTLCompileOptions* const options = [MTLCompileOptions new]; + NSMutableDictionary* const macros = [NSMutableDictionary dictionary]; if (key.msaaColorSource) { macros[@"MSAA_COLOR_SOURCE"] = @"1"; } - if (key.msaaDepthSource) { - macros[@"MSAA_DEPTH_SOURCE"] = @"1"; - } if (key.sources3D) { macros[@"SOURCES_3D"] = @"1"; } options.preprocessorMacros = macros; - NSString* objcSource = [NSString stringWithCString:functionLibrary - encoding:NSUTF8StringEncoding]; + NSString* const objcSource = [NSString stringWithCString:functionLibrary + encoding:NSUTF8StringEncoding]; NSError* error = nil; - id library = [mContext.device newLibraryWithSource:objcSource - options:options - error:&error]; - id function = [library newFunctionWithName:@"blitterFrag"]; + id const library = [mContext.device newLibraryWithSource:objcSource + options:options + error:&error]; + id const function = [library newFunctionWithName:@"blitterFrag"]; if (!library || !function) { if (error) { @@ -501,13 +337,14 @@ return mVertexFunction; } - NSString* objcSource = [NSString stringWithCString:functionLibrary - encoding:NSUTF8StringEncoding]; + NSString* const objcSource = [NSString stringWithCString:functionLibrary + encoding:NSUTF8StringEncoding]; NSError* error = nil; - id library = [mContext.device newLibraryWithSource:objcSource - options:nil - error:&error]; - id function = [library newFunctionWithName:@"blitterVertex"]; + id const library = [mContext.device newLibraryWithSource:objcSource + options:nil + error:&error]; + + id const function = [library newFunctionWithName:@"blitterVertex"]; if (!library || !function) { if (error) { @@ -535,5 +372,5 @@ return function; } -} // namespace backend -} // namespace filament +} // 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 2f494ba9974..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; @@ -73,7 +75,10 @@ class MetalBufferPool { std::unordered_set mUsedStages; // Store the current "time" (really just a frame count) and LRU eviction parameters. - uint64_t mCurrentFrame = 0; + // An atomic is necessary as mCurrentFrame is incremented in gc() (called on + // the driver thread) and read from acquireBuffer() and releaseBuffer(), + // which may be called on non-driver threads. + std::atomic mCurrentFrame = 0; static constexpr uint32_t TIME_BEFORE_EVICTION = 10; }; diff --git a/filament/backend/src/metal/MetalBufferPool.mm b/filament/backend/src/metal/MetalBufferPool.mm index ce29b4222fb..3b75c8e85d4 100644 --- a/filament/backend/src/metal/MetalBufferPool.mm +++ b/filament/backend/src/metal/MetalBufferPool.mm @@ -19,6 +19,7 @@ #include "MetalContext.h" #include +#include #include #include @@ -44,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/MetalContext.h b/filament/backend/src/metal/MetalContext.h index 7b118f95bc1..33b8f92c829 100644 --- a/filament/backend/src/metal/MetalContext.h +++ b/filament/backend/src/metal/MetalContext.h @@ -18,12 +18,15 @@ #define TNT_METALCONTEXT_H #include "MetalResourceTracker.h" +#include "MetalShaderCompiler.h" #include "MetalState.h" #include #include #include +#include + #include #include #include @@ -53,6 +56,9 @@ struct MetalVertexBuffer; constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples struct MetalContext { + explicit MetalContext(size_t metalFreedTextureListSize) + : texturesToDestroy(metalFreedTextureListSize) {} + MetalDriver* driver; id device = nullptr; id commandQueue = nullptr; @@ -93,6 +99,7 @@ struct MetalContext { std::array ssboState; CullModeStateTracker cullModeState; WindingStateTracker windingState; + Handle currentRenderPrimitive; // State caches. DepthStencilStateCache depthStencilStateCache; @@ -111,6 +118,14 @@ struct MetalContext { tsl::robin_set samplerGroups; tsl::robin_set textures; + // This circular buffer implements delayed destruction for Metal texture handles. It keeps a + // handle to a fixed number of the most recently destroyed texture handles. When we're asked to + // destroy a texture handle, we free its texture memory, but keep the MetalTexture object alive, + // marking it as "terminated". If we later are asked to use that texture, we can check its + // terminated status and throw an Objective-C error instead of crashing, which is helpful for + // debugging use-after-free issues in release builds. + utils::FixedCircularBuffer> texturesToDestroy; + MetalBufferPool* bufferPool; MetalSwapChain* currentDrawSwapChain = nil; @@ -128,7 +143,10 @@ struct MetalContext { // Fences, only supported on macOS 10.14 and iOS 12 and above. API_AVAILABLE(macos(10.14), ios(12.0)) MTLSharedEventListener* eventListener = nil; - uint64_t signalId = 1; + // signalId is incremented in the MetalFence constructor, which is called on + // both the driver (MetalTimerQueryFence::beginTimeElapsedQuery) and main + // threads (in createFenceS), so an atomic is necessary. + std::atomic signalId = 1; MetalTimerQueryInterface* timerQueryImpl; @@ -136,6 +154,8 @@ struct MetalContext { MTLViewport currentViewport; + MetalShaderCompiler* shaderCompiler = nullptr; + #if defined(FILAMENT_METAL_PROFILING) // Logging and profiling. os_log_t log; diff --git a/filament/backend/src/metal/MetalDriver.h b/filament/backend/src/metal/MetalDriver.h index 9f48243bdf4..51cd7902ddb 100644 --- a/filament/backend/src/metal/MetalDriver.h +++ b/filament/backend/src/metal/MetalDriver.h @@ -28,17 +28,21 @@ #include #include +#include +#include +#include + namespace filament { namespace backend { class MetalPlatform; class MetalBuffer; +class MetalProgram; class MetalSamplerGroup; class MetalTexture; struct MetalUniformBuffer; struct MetalContext; -struct MetalProgram; struct BufferState; #ifndef FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB @@ -52,13 +56,13 @@ class MetalDriver final : public DriverBase { public: static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig); + void runAtNextTick(const std::function& fn) noexcept; private: friend class MetalSwapChain; MetalPlatform& mPlatform; - MetalContext* mContext; ShaderModel getShaderModel() const noexcept final; @@ -66,6 +70,13 @@ class MetalDriver final : public DriverBase { // Overrides the default implementation by wrapping the call to fn in an @autoreleasepool block. void execute(std::function const& fn) noexcept final; + /* + * Tasks run regularly on the driver thread. + */ + void executeTickOps() noexcept; + std::vector> mTickOps; + std::mutex mTickOpsLock; + /* * Driver interface */ @@ -122,12 +133,9 @@ class MetalDriver final : public DriverBase { mHandleAllocator.deallocate(handle, p); } - inline void setRenderPrimitiveBuffer(Handle rph, + inline void setRenderPrimitiveBuffer(Handle rph, PrimitiveType pt, Handle vbh, Handle ibh); - inline void setRenderPrimitiveRange(Handle rph, PrimitiveType pt, - uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count); - void finalizeSamplerGroup(MetalSamplerGroup* sg); void enumerateBoundBuffers(BufferObjectBinding bindingType, const std::function& f); diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index d14d9fb900c..f99a6b63a66 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 @@ -43,6 +45,42 @@ namespace backend { Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform::DriverConfig& driverConfig) { +#if 0 + // this is useful for development, but too verbose even for debug builds + // For reference on a 64-bits machine in Release mode: + // MetalTimerQuery : 16 few + // HwStream : 24 few + // MetalRenderPrimitive : 24 many + // MetalVertexBuffer : 32 moderate + // -- less than or equal 32 bytes + // MetalIndexBuffer : 40 moderate + // MetalFence : 48 few + // MetalBufferObject : 48 many + // -- less than or equal 48 bytes + // MetalSamplerGroup : 112 few + // MetalProgram : 152 moderate + // MetalTexture : 152 moderate + // MetalSwapChain : 184 few + // MetalRenderTarget : 272 few + // MetalVertexBufferInfo : 552 moderate + // -- less than or equal to 552 bytes + + utils::slog.d + << "\nMetalSwapChain: " << sizeof(MetalSwapChain) + << "\nMetalBufferObject: " << sizeof(MetalBufferObject) + << "\nMetalVertexBuffer: " << sizeof(MetalVertexBuffer) + << "\nMetalVertexBufferInfo: " << sizeof(MetalVertexBufferInfo) + << "\nMetalIndexBuffer: " << sizeof(MetalIndexBuffer) + << "\nMetalSamplerGroup: " << sizeof(MetalSamplerGroup) + << "\nMetalRenderPrimitive: " << sizeof(MetalRenderPrimitive) + << "\nMetalTexture: " << sizeof(MetalTexture) + << "\nMetalTimerQuery: " << sizeof(MetalTimerQuery) + << "\nHwStream: " << sizeof(HwStream) + << "\nMetalRenderTarget: " << sizeof(MetalRenderTarget) + << "\nMetalFence: " << sizeof(MetalFence) + << "\nMetalProgram: " << sizeof(MetalProgram) + << utils::io::endl; +#endif return MetalDriver::create(platform, driverConfig); } @@ -50,7 +88,8 @@ Driver* MetalDriver::create(MetalPlatform* const platform, const Platform::DriverConfig& driverConfig) { assert_invariant(platform); size_t defaultSize = FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U; - Platform::DriverConfig validConfig { .handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize) }; + Platform::DriverConfig validConfig {driverConfig}; + validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize); return new MetalDriver(platform, validConfig); } @@ -60,8 +99,10 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept : mPlatform(*platform), - mContext(new MetalContext), - mHandleAllocator("Handles", driverConfig.handleArenaSize) { + mContext(new MetalContext(driverConfig.textureUseAfterFreePoolSize)), + mHandleAllocator("Handles", + driverConfig.handleArenaSize, + driverConfig.disableHandleUseAfterFreeCheck) { mContext->driver = this; mContext->device = mPlatform.createDevice(); @@ -143,6 +184,12 @@ mContext->eventListener = [[MTLSharedEventListener alloc] initWithDispatchQueue:queue]; } + const MetalShaderCompiler::Mode compilerMode = driverConfig.disableParallelShaderCompile + ? MetalShaderCompiler::Mode::SYNCHRONOUS + : MetalShaderCompiler::Mode::ASYNCHRONOUS; + mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this, compilerMode); + mContext->shaderCompiler->init(); + #if defined(FILAMENT_METAL_PROFILING) mContext->log = os_log_create("com.google.filament", "Metal"); mContext->signpostId = os_signpost_id_generate(mContext->log); @@ -157,16 +204,21 @@ delete mContext->bufferPool; delete mContext->blitter; delete mContext->timerQueryImpl; + delete mContext->shaderCompiler; delete mContext; } void MetalDriver::tick(int) { + executeTickOps(); } void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) { #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, @@ -239,10 +291,16 @@ [oneOffBuffer waitUntilCompleted]; } -void MetalDriver::createVertexBufferR(Handle vbh, uint8_t bufferCount, - uint8_t attributeCount, uint32_t vertexCount, AttributeArray attributes) { - construct_handle(vbh, *mContext, bufferCount, - attributeCount, vertexCount, attributes); +void MetalDriver::createVertexBufferInfoR(Handle vbih, uint8_t bufferCount, + uint8_t attributeCount, AttributeArray attributes) { + construct_handle(vbih, *mContext, + bufferCount, attributeCount, attributes); +} + +void MetalDriver::createVertexBufferR(Handle vbh, + uint32_t vertexCount, Handle vbih) { + MetalVertexBufferInfo const* const vbi = handle_cast(vbih); + construct_handle(vbh, *mContext, vertexCount, vbi->bufferCount, vbih); } void MetalDriver::createIndexBufferR(Handle ibh, ElementType elementType, @@ -303,21 +361,20 @@ target, levels, format, samples, width, height, depth, usage, metalTexture)); } -void MetalDriver::createSamplerGroupR(Handle sbh, uint32_t size) { - mContext->samplerGroups.insert(construct_handle(sbh, size)); +void MetalDriver::createSamplerGroupR( + Handle sbh, uint32_t size, utils::FixedSizeString<32> debugName) { + mContext->samplerGroups.insert(construct_handle(sbh, size, debugName)); } void MetalDriver::createRenderPrimitiveR(Handle rph, Handle vbh, Handle ibh, - PrimitiveType pt, uint32_t offset, - uint32_t minIndex, uint32_t maxIndex, uint32_t count) { + PrimitiveType pt) { construct_handle(rph); - MetalDriver::setRenderPrimitiveBuffer(rph, vbh, ibh); - MetalDriver::setRenderPrimitiveRange(rph, pt, offset, minIndex, maxIndex, count); + MetalDriver::setRenderPrimitiveBuffer(rph, pt, vbh, ibh); } void MetalDriver::createProgramR(Handle rph, Program&& program) { - construct_handle(rph, mContext->device, program); + construct_handle(rph, *mContext, std::move(program)); } void MetalDriver::createDefaultRenderTargetR(Handle rth, int dummy) { @@ -326,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."); @@ -399,6 +456,10 @@ // nothing to do, timer query was constructed in createTimerQueryS } +Handle MetalDriver::createVertexBufferInfoS() noexcept { + return alloc_handle(); +} + Handle MetalDriver::createVertexBufferS() noexcept { return alloc_handle(); } @@ -463,6 +524,12 @@ return alloc_and_construct_handle(); } +void MetalDriver::destroyVertexBufferInfo(Handle vbih) { + if (vbih) { + destruct_handle(vbih); + } +} + void MetalDriver::destroyVertexBuffer(Handle vbh) { if (vbh) { destruct_handle(vbh); @@ -530,8 +597,18 @@ return; } - mContext->textures.erase(handle_cast(th)); - destruct_handle(th); + auto* metalTexture = handle_cast(th); + mContext->textures.erase(metalTexture); + + // Free memory from the texture and mark it as freed. + metalTexture->terminate(); + + // Add this texture handle to our texturesToDestroy queue to be destroyed later. + if (auto handleToFree = mContext->texturesToDestroy.push(th)) { + // If texturesToDestroy is full, then .push evicts the oldest texture handle in the + // queue (or simply th, if use-after-free detection is disabled). + destruct_handle(handleToFree.value()); + } } void MetalDriver::destroyRenderTarget(Handle rth) { @@ -557,15 +634,24 @@ } void MetalDriver::terminate() { + // Terminate any outstanding MetalTextures. + while (!mContext->texturesToDestroy.empty()) { + Handle toDestroy = mContext->texturesToDestroy.pop(); + destruct_handle(toDestroy); + } + // finish() will flush the pending command buffer and will ensure all GPU work has finished. // This must be done before calling bufferPool->reset() to ensure no buffers are in flight. finish(); + executeTickOps(); + mContext->bufferPool->reset(); mContext->commandQueue = nil; MetalExternalImage::shutdown(*mContext); mContext->blitter->shutdown(); + mContext->shaderCompiler->terminate(); } ShaderModel MetalDriver::getShaderModel() const noexcept { @@ -696,11 +782,30 @@ return false; } -bool MetalDriver::isStereoSupported() { - return true; +bool MetalDriver::isProtectedContentSupported() { + // the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported + return false; +} + +bool MetalDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) { + switch (stereoscopicType) { + case backend::StereoscopicType::INSTANCED: + return true; + case backend::StereoscopicType::MULTIVIEW: + // TODO: implement multiview feature in Metal. + return false; + } } bool MetalDriver::isParallelShaderCompileSupported() { + return mContext->shaderCompiler->isParallelShaderCompileSupported(); +} + +bool MetalDriver::isDepthStencilResolveSupported() { + return false; +} + +bool MetalDriver::isProtectedTexturesSupported() { return false; } @@ -830,9 +935,10 @@ void MetalDriver::setExternalStream(Handle th, Handle sh) { } -bool MetalDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { +TimerQueryResult MetalDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { auto* tq = handle_cast(tqh); - return mContext->timerQueryImpl->getQueryResult(tq, elapsedTime); + return mContext->timerQueryImpl->getQueryResult(tq, elapsedTime) ? + TimerQueryResult::AVAILABLE : TimerQueryResult::NOT_READY; } void MetalDriver::generateMipmaps(Handle th) { @@ -842,10 +948,6 @@ tex->generateMipmaps(); } -bool MetalDriver::canGenerateMipmaps() { - return true; -} - void MetalDriver::updateSamplerGroup(Handle sbh, BufferDescriptor&& data) { ASSERT_PRECONDITION(!isInRenderPass(mContext), "updateSamplerGroup must be called outside of a render pass."); @@ -854,13 +956,17 @@ assert_invariant(sb->size == data.size / sizeof(SamplerDescriptor)); auto const* const samplers = (SamplerDescriptor const*) data.buffer; -#ifndef NDEBUG - // In debug builds, verify that all the textures in the sampler group are still alive. + // Verify that all the textures in the sampler group are still alive. // These bugs lead to memory corruption and can be difficult to track down. for (size_t s = 0; s < data.size / sizeof(SamplerDescriptor); s++) { if (!samplers[s].t) { continue; } + // The difference between this check and the one below is that in release, we do this for + // only a set number of recently freed textures, while the debug check is exhaustive. + auto* metalTexture = handle_cast(samplers[s].t); + metalTexture->checkUseAfterFree(sb->debugName.c_str(), s); +#ifndef NDEBUG auto iter = mContext->textures.find(handle_cast(samplers[s].t)); if (iter == mContext->textures.end()) { utils::slog.e << "updateSamplerGroup: texture #" @@ -868,8 +974,8 @@ << samplers[s].t << utils::io::endl; } assert_invariant(iter != mContext->textures.end()); - } #endif + } // Create a MTLArgumentEncoder for these textures. // Ideally, we would create this encoder at createSamplerGroup time, but we need to know the @@ -935,7 +1041,7 @@ void MetalDriver::compilePrograms(CompilerPriorityQueue priority, CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { if (callback) { - scheduleCallback(handler, user, callback); + mContext->shaderCompiler->notifyWhenAllProgramsAreReady(handler, callback, user); } } @@ -999,23 +1105,14 @@ mContext->currentRenderPassEncoder = nil; } -void MetalDriver::setRenderPrimitiveBuffer(Handle rph, +void MetalDriver::setRenderPrimitiveBuffer(Handle rph, PrimitiveType pt, Handle vbh, Handle ibh) { auto primitive = handle_cast(rph); auto vertexBuffer = handle_cast(vbh); auto indexBuffer = handle_cast(ibh); - primitive->setBuffers(vertexBuffer, indexBuffer); -} - -void MetalDriver::setRenderPrimitiveRange(Handle rph, - PrimitiveType pt, uint32_t offset, uint32_t minIndex, uint32_t maxIndex, - uint32_t count) { - auto primitive = handle_cast(rph); + MetalVertexBufferInfo const* const vbi = handle_cast(vertexBuffer->vbih); + primitive->setBuffers(vbi, vertexBuffer, indexBuffer); primitive->type = pt; - primitive->offset = offset * primitive->indexBuffer->elementSize; - primitive->count = count; - primitive->minIndex = minIndex; - primitive->maxIndex = maxIndex > minIndex ? maxIndex : primitive->maxVertexCount - 1; } void MetalDriver::makeCurrent(Handle schDraw, Handle schRead) { @@ -1229,14 +1326,14 @@ textureDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget; id readPixelsTexture = [mContext->device newTextureWithDescriptor:textureDescriptor]; - MetalBlitter::BlitArgs args; + MetalBlitter::BlitArgs args{}; args.filter = SamplerMagFilter::NEAREST; args.source.level = miplevel; args.source.region = MTLRegionMake2D(0, 0, srcTexture.width >> miplevel, srcTexture.height >> miplevel); + args.source.texture = srcTexture; args.destination.level = 0; args.destination.region = MTLRegionMake2D(0, 0, readPixelsTexture.width, readPixelsTexture.height); - args.source.color = srcTexture; - args.destination.color = readPixelsTexture; + args.destination.texture = readPixelsTexture; mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "readPixels blit"); @@ -1272,24 +1369,123 @@ scheduleDestroy(std::move(p)); } -void MetalDriver::blit(TargetBufferFlags buffers, +void MetalDriver::resolve( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, + Handle src, uint8_t dstLevel, uint8_t dstLayer) { + auto* const srcTexture = handle_cast(src); + auto* const dstTexture = handle_cast(dst); + assert_invariant(srcTexture); + assert_invariant(dstTexture); + + ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil, + "resolve() cannot be invoked inside a render pass."); + + ASSERT_PRECONDITION( + dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height, + "invalid resolve: src and dst sizes don't match"); + + ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1, + "invalid resolve: src.samples=%u, dst.samples=%u", + +srcTexture->samples, +dstTexture->samples); + + ASSERT_PRECONDITION(srcTexture->format == dstTexture->format, + "src and dst texture format don't match"); + + ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format), + "can't resolve depth formats"); + + ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format), + "can't resolve stencil formats"); + + ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST), + "texture doesn't have BLIT_DST"); + + ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC), + "texture doesn't have BLIT_SRC"); + + // FIXME: on metal the blit() call below always take the slow path (using a shader) + + blit( dst, dstLevel, dstLayer, {}, + src, srcLevel, srcLayer, {}, + { dstTexture->width, dstTexture->height }); +} + +void MetalDriver::blit( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin, + Handle src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin, + math::uint2 size) { + + + auto isBlitableTextureType = [](MTLTextureType t) -> bool { + return t == MTLTextureType2D || t == MTLTextureType2DMultisample || + t == MTLTextureType2DArray; + }; + + auto* const srcTexture = handle_cast(src); + auto* const dstTexture = handle_cast(dst); + assert_invariant(srcTexture); + assert_invariant(dstTexture); + + ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil, + "blit() cannot be invoked inside a render pass."); + + ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST), + "texture doesn't have BLIT_DST"); + + ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC), + "texture doesn't have BLIT_SRC"); + + ASSERT_PRECONDITION(srcTexture->format == dstTexture->format, + "src and dst texture format don't match"); + + ASSERT_PRECONDITION(isBlitableTextureType(srcTexture->getMtlTextureForRead().textureType) && + isBlitableTextureType(dstTexture->getMtlTextureForWrite().textureType), + "Metal does not support blitting to/from non-2D textures."); + + MetalBlitter::BlitArgs args{}; + args.filter = SamplerMagFilter::NEAREST; + args.source.region = MTLRegionMake2D( + (NSUInteger)srcOrigin.x, + std::max(srcTexture->height - (int64_t)srcOrigin.y - size.y, (int64_t)0), + size.x, size.y); + args.destination.region = MTLRegionMake2D( + (NSUInteger)dstOrigin.x, + std::max(dstTexture->height - (int64_t)dstOrigin.y - size.y, (int64_t)0), + size.x, size.y); + + // FIXME: we shouldn't need to know the type here. This is an artifact of using the old API. + + args.source.texture = srcTexture->getMtlTextureForRead(); + args.destination.texture = dstTexture->getMtlTextureForWrite(); + args.source.level = srcLevel; + args.source.slice = srcLayer; + args.destination.level = dstLevel; + args.destination.slice = dstLayer; + + // TODO: The blit() call below always take the fast path. + + mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blit/resolve"); + + dstTexture->extendLodRangeTo(dstLevel); +} + +void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers, Handle dst, Viewport dstRect, Handle src, Viewport srcRect, SamplerMagFilter filter) { - // If we're the in middle of a render pass, finish it. - // This condition should only occur during copyFrame. It's okay to end the render pass because - // we don't issue any other rendering commands. - if (mContext->currentRenderPassEncoder) { - [mContext->currentRenderPassEncoder endEncoding]; - mContext->currentRenderPassEncoder = nil; - } + + // Note: blitDEPRECATED is only used by Renderer::copyFrame + // It is called between beginFrame and endFrame, but should never be called in the middle of + // a render pass. + + ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil, + "blitDEPRECATED() cannot be invoked inside a render pass."); auto srcTarget = handle_cast(src); auto dstTarget = handle_cast(dst); - ASSERT_PRECONDITION( - !(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)), - "Blitting only supports COLOR0"); + ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0, + "blitDEPRECATED only supports COLOR0"); ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 && dstRect.left >= 0 && dstRect.bottom >= 0, @@ -1300,54 +1496,28 @@ t == MTLTextureType2DArray; }; - // MetalBlitter supports blitting color and depth simultaneously, but for simplicitly we'll blit - // them separately. In practice, Filament only ever blits a single buffer at a time anyway. - - if (any(buffers & TargetBufferFlags::COLOR_ALL)) { - // We always blit from/to the COLOR0 attachment. - MetalRenderTarget::Attachment srcColorAttachment = srcTarget->getReadColorAttachment(0); - MetalRenderTarget::Attachment dstColorAttachment = dstTarget->getDrawColorAttachment(0); - - if (srcColorAttachment && dstColorAttachment) { - ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) && - isBlitableTextureType(dstColorAttachment.getTexture().textureType), - "Metal does not support blitting to/from non-2D textures."); - - MetalBlitter::BlitArgs args; - args.filter = filter; - args.source.region = srcTarget->getRegionFromClientRect(srcRect); - args.destination.region = dstTarget->getRegionFromClientRect(dstRect); - args.source.color = srcColorAttachment.getTexture(); - args.destination.color = dstColorAttachment.getTexture(); - args.source.level = srcColorAttachment.level; - args.destination.level = dstColorAttachment.level; - args.source.slice = srcColorAttachment.layer; - args.destination.slice = dstColorAttachment.layer; - mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Color blit"); - } - } + // We always blit from/to the COLOR0 attachment. + MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0); + MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0); - if (any(buffers & TargetBufferFlags::DEPTH)) { - MetalRenderTarget::Attachment srcDepthAttachment = srcTarget->getDepthAttachment(); - MetalRenderTarget::Attachment dstDepthAttachment = dstTarget->getDepthAttachment(); - - if (srcDepthAttachment && dstDepthAttachment) { - ASSERT_PRECONDITION(isBlitableTextureType(srcDepthAttachment.getTexture().textureType) && - isBlitableTextureType(dstDepthAttachment.getTexture().textureType), - "Metal does not support blitting to/from non-2D textures."); - - MetalBlitter::BlitArgs args; - args.filter = filter; - args.source.region = srcTarget->getRegionFromClientRect(srcRect); - args.destination.region = dstTarget->getRegionFromClientRect(dstRect); - args.source.depth = srcDepthAttachment.getTexture(); - args.destination.depth = dstDepthAttachment.getTexture(); - args.source.level = srcDepthAttachment.level; - args.destination.level = dstDepthAttachment.level; - args.source.slice = srcDepthAttachment.layer; - args.destination.slice = dstDepthAttachment.layer; - mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Depth blit"); - } + if (srcColorAttachment && dstColorAttachment) { + ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) && + isBlitableTextureType(dstColorAttachment.getTexture().textureType), + "Metal does not support blitting to/from non-2D textures."); + + MetalBlitter::BlitArgs args{}; + args.filter = filter; + args.source.region = srcTarget->getRegionFromClientRect(srcRect); + args.source.texture = srcColorAttachment.getTexture(); + args.source.level = srcColorAttachment.level; + args.source.slice = srcColorAttachment.layer; + + args.destination.region = dstTarget->getRegionFromClientRect(dstRect); + args.destination.texture = dstColorAttachment.getTexture(); + args.destination.level = dstColorAttachment.level; + args.destination.slice = dstColorAttachment.layer; + + mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED"); } } @@ -1357,23 +1527,27 @@ id cmdBuffer = getPendingCommandBuffer(mContext); -#ifndef NDEBUG - // In debug builds, verify that all the textures in the sampler group are still alive. + // Verify that all the textures in the sampler group are still alive. // These bugs lead to memory corruption and can be difficult to track down. const auto& handles = samplerGroup->getTextureHandles(); for (size_t s = 0; s < handles.size(); s++) { if (!handles[s]) { continue; } - auto iter = mContext->textures.find(handle_cast(handles[s])); + // The difference between this check and the one below is that in release, we do this for + // only a set number of recently freed textures, while the debug check is exhaustive. + auto* metalTexture = handle_cast(handles[s]); + metalTexture->checkUseAfterFree(samplerGroup->debugName.c_str(), s); +#ifndef NDEBUG + auto iter = mContext->textures.find(metalTexture); if (iter == mContext->textures.end()) { utils::slog.e << "finalizeSamplerGroup: texture #" << (int) s << " is dead, texture handle = " << handles[s] << utils::io::endl; } assert_invariant(iter != mContext->textures.end()); - } #endif + } utils::FixedCapacityVector> newTextures(samplerGroup->size, nil); for (size_t binding = 0; binding < samplerGroup->size; binding++) { @@ -1440,21 +1614,29 @@ } } -void MetalDriver::draw(PipelineState ps, Handle rph, uint32_t instanceCount) { +void MetalDriver::bindPipeline(PipelineState ps) { ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr, - "Attempted to draw without a valid command encoder."); - auto primitive = handle_cast(rph); + "bindPipeline() without a valid command encoder."); + + MetalVertexBufferInfo const* const vbi = + handle_cast(ps.vertexBufferInfo); + auto program = handle_cast(ps.program); const auto& rs = ps.rasterState; + // This might block until the shader compilation has finished. + auto functions = program->getFunctions(); + // If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur // during the draw call when the program is invalid. The shader compile error has already been // dumped to the console at this point, so it's fine to simply return early. - if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!program->isValid)) { + if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!functions)) { return; } - ASSERT_PRECONDITION(program->isValid, "Attempting to draw with an invalid Metal program."); + functions.validate(); + + auto [fragment, vertex] = functions.getRasterFunctions(); // Pipeline state MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid }; @@ -1476,10 +1658,10 @@ stencilPixelFormat = stencilAttachment.getPixelFormat(); assert_invariant(isMetalFormatStencil(stencilPixelFormat)); } - MetalPipelineState pipelineState { - .vertexFunction = program->vertexFunction, - .fragmentFunction = program->fragmentFunction, - .vertexDescription = primitive->vertexDescription, + MetalPipelineState const pipelineState { + .vertexFunction = vertex, + .fragmentFunction = fragment, + .vertexDescription = vbi->vertexDescription, .colorAttachmentPixelFormat = { colorPixelFormat[0], colorPixelFormat[1], @@ -1494,13 +1676,13 @@ .stencilAttachmentPixelFormat = stencilPixelFormat, .sampleCount = mContext->currentRenderTarget->getSamples(), .blendState = BlendState { - .blendingEnabled = rs.hasBlending(), - .rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB), .alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha), - .sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB), - .sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha), + .rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB), + .destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha), .destinationRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionDstRGB), - .destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha) + .sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha), + .sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB), + .blendingEnabled = rs.hasBlending(), }, .colorWrite = rs.colorWrite }; @@ -1573,57 +1755,13 @@ mContext->currentPolygonOffset = ps.polygonOffset; } - // Set scissor-rectangle. - // In order to do this, we compute the intersection between: - // 1. the scissor rectangle - // 2. the render target attachment dimensions (important, as the scissor can't be set larger) - // fmax/min are used below to guard against NaN and because the MTLViewport/MTLRegion - // coordinates are doubles. - MTLRegion scissor = mContext->currentRenderTarget->getRegionFromClientRect(ps.scissor); - const float sleft = scissor.origin.x, sright = scissor.origin.x + scissor.size.width; - const float stop = scissor.origin.y, sbottom = scissor.origin.y + scissor.size.height; - - // Attachment extent - const auto attachmentSize = mContext->currentRenderTarget->getAttachmentSize(); - const float aleft = 0.0f, atop = 0.0f; - const float aright = static_cast(attachmentSize.x); - const float abottom = static_cast(attachmentSize.y); - - const auto left = std::fmax(sleft, aleft); - const auto right = std::fmin(sright, aright); - const auto top = std::fmax(stop, atop); - const auto bottom = std::fmin(sbottom, abottom); - - MTLScissorRect scissorRect = { - .x = static_cast(left), - .y = static_cast(top), - .width = static_cast(right - left), - .height = static_cast(bottom - top) - }; - - [mContext->currentRenderPassEncoder setScissorRect:scissorRect]; - - // Bind uniform buffers. - MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil }; - NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 }; - - enumerateBoundBuffers(BufferObjectBinding::UNIFORM, - [&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer, - uint32_t index) { - uniformsToBind[index] = buffer; - offsets[index] = state.offset; - }); - MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder, - UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT, - uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT); - // Bind sampler groups (argument buffers). for (size_t s = 0; s < Program::SAMPLER_BINDING_COUNT; s++) { MetalSamplerGroup* const samplerGroup = mContext->samplerBindings[s]; if (!samplerGroup) { continue; } - const auto& stageFlags = program->samplerGroupInfo[s].stageFlags; + const auto& stageFlags = program->getSamplerGroupInfo()[s].stageFlags; if (stageFlags == ShaderStageFlags::NONE) { continue; } @@ -1647,29 +1785,36 @@ atIndex:(SAMPLER_GROUP_BINDING_START + s)]; } } +} - // Bind the user vertex buffers. +void MetalDriver::bindRenderPrimitive(Handle rph) { + ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr, + "bindRenderPrimitive() without a valid command encoder."); - MetalBuffer* buffers[MAX_VERTEX_BUFFER_COUNT] = {}; + // Bind the user vertex buffers. + MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {}; size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {}; - size_t bufferIndex = 0; + size_t maxBufferIndex = 0; - auto vb = primitive->vertexBuffer; - for (uint32_t attributeIndex = 0; attributeIndex < vb->attributes.size(); attributeIndex++) { - const auto& attribute = vb->attributes[attributeIndex]; - if (attribute.buffer == Attribute::BUFFER_UNUSED) { - continue; - } + MetalRenderPrimitive const* const primitive = handle_cast(rph); + MetalVertexBufferInfo const* const vbi = + handle_cast(primitive->vertexBuffer->vbih); + + mContext->currentRenderPrimitive = rph; - assert_invariant(vb->buffers[attribute.buffer]); - buffers[bufferIndex] = vb->buffers[attribute.buffer]; - vertexBufferOffsets[bufferIndex] = attribute.offset; - bufferIndex++; + auto vb = primitive->vertexBuffer; + for (auto m : vbi->bufferMapping) { + assert_invariant( + m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START && + m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT); + size_t const vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START; + vertexBuffers[vertexBufferIndex] = vb->buffers[m.sourceBufferIndex]; + maxBufferIndex = std::max(maxBufferIndex, vertexBufferIndex); } - const auto bufferCount = bufferIndex; + const auto bufferCount = maxBufferIndex + 1; MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder, - USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, buffers, + USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, vertexBuffers, vertexBufferOffsets, bufferCount); // Bind the zero buffer, used for missing vertex attributes. @@ -1677,40 +1822,76 @@ [mContext->currentRenderPassEncoder setVertexBytes:bytes length:16 atIndex:ZERO_VERTEX_BUFFER_BINDING]; +} + +void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { + ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr, + "draw() without a valid command encoder."); + + // Bind uniform buffers. + MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil }; + NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 }; + + enumerateBoundBuffers(BufferObjectBinding::UNIFORM, + [&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer, + uint32_t index) { + uniformsToBind[index] = buffer; + offsets[index] = state.offset; + }); + MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder, + UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT, + uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT); + + auto primitive = handle_cast(mContext->currentRenderPrimitive); MetalIndexBuffer* indexBuffer = primitive->indexBuffer; id cmdBuffer = getPendingCommandBuffer(mContext); id metalIndexBuffer = indexBuffer->buffer.getGpuBufferForDraw(cmdBuffer); [mContext->currentRenderPassEncoder drawIndexedPrimitives:getMetalPrimitiveType(primitive->type) - indexCount:primitive->count + indexCount:indexCount indexType:getIndexType(indexBuffer->elementSize) indexBuffer:metalIndexBuffer - indexBufferOffset:primitive->offset + indexBufferOffset:indexOffset * primitive->indexBuffer->elementSize instanceCount:instanceCount]; } +void MetalDriver::draw(PipelineState ps, Handle rph, + uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) { + MetalRenderPrimitive const* const rp = handle_cast(rph); + ps.primitiveType = rp->type; + ps.vertexBufferInfo = rp->vertexBuffer->vbih; + bindPipeline(ps); + bindRenderPrimitive(rph); + draw2(indexOffset, indexCount, instanceCount); +} + void MetalDriver::dispatchCompute(Handle program, math::uint3 workGroupCount) { ASSERT_PRECONDITION(!isInRenderPass(mContext), "dispatchCompute must be called outside of a render pass."); auto mtlProgram = handle_cast(program); + // This might block until the shader compilation has finished. + auto functions = mtlProgram->getFunctions(); + // If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur // during the draw call when the program is invalid. The shader compile error has already been // dumped to the console at this point, so it's fine to simply return early. - if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!mtlProgram->isValid)) { + if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!functions)) { return; } - assert_invariant(mtlProgram->isValid && mtlProgram->computeFunction); + auto compute = functions.getComputeFunction(); + + assert_invariant(bool(functions) && compute); id computeEncoder = [getPendingCommandBuffer(mContext) computeCommandEncoder]; NSError* error = nil; id computePipelineState = - [mContext->device newComputePipelineStateWithFunction:mtlProgram->computeFunction + [mContext->device newComputePipelineStateWithFunction:compute error:&error]; if (error) { auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]; @@ -1754,6 +1935,38 @@ [computeEncoder endEncoding]; } +void MetalDriver::scissor(Viewport scissorBox) { + // Set scissor-rectangle. + // In order to do this, we compute the intersection between: + // 1. the scissor rectangle + // 2. the render target attachment dimensions (important, as the scissor can't be set larger) + // fmax/min are used below to guard against NaN and because the MTLViewport/MTLRegion + // coordinates are doubles. + MTLRegion scissor = mContext->currentRenderTarget->getRegionFromClientRect(scissorBox); + const float sleft = scissor.origin.x, sright = scissor.origin.x + scissor.size.width; + const float stop = scissor.origin.y, sbottom = scissor.origin.y + scissor.size.height; + + // Attachment extent + const auto attachmentSize = mContext->currentRenderTarget->getAttachmentSize(); + const float aleft = 0.0f, atop = 0.0f; + const float aright = static_cast(attachmentSize.x); + const float abottom = static_cast(attachmentSize.y); + + const auto left = std::fmax(sleft, aleft); + const auto right = std::fmin(sright, aright); + const auto top = std::fmax(stop, atop); + const auto bottom = std::fmin(sbottom, abottom); + + MTLScissorRect scissorRect = { + .x = static_cast(left), + .y = static_cast(top), + .width = static_cast(right - left), + .height = static_cast(bottom - top) + }; + + [mContext->currentRenderPassEncoder setScissorRect:scissorRect]; +} + void MetalDriver::beginTimerQuery(Handle tqh) { ASSERT_PRECONDITION(!isInRenderPass(mContext), "beginTimerQuery must be called outside of a render pass."); @@ -1797,6 +2010,21 @@ void MetalDriver::resetState(int) { } +void MetalDriver::runAtNextTick(const std::function& fn) noexcept { + std::lock_guard const lock(mTickOpsLock); + mTickOps.push_back(fn); +} + +void MetalDriver::executeTickOps() noexcept { + std::vector> ops; + mTickOpsLock.lock(); + std::swap(ops, mTickOps); + mTickOpsLock.unlock(); + for (const auto& f : ops) { + f(); + } +} + // explicit instantiation of the Dispatcher template class ConcreteDispatcher; diff --git a/filament/backend/src/metal/MetalExternalImage.mm b/filament/backend/src/metal/MetalExternalImage.mm index 329e3bc0e8f..a5481cc7456 100644 --- a/filament/backend/src/metal/MetalExternalImage.mm +++ b/filament/backend/src/metal/MetalExternalImage.mm @@ -21,6 +21,7 @@ #include "MetalUtils.h" #include +#include #include #define NSERROR_CHECK(message) \ diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index cd3a4e8c68d..fea6f0947f9 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -32,6 +32,7 @@ #include "private/backend/SamplerGroup.h" #include +#include #include #include @@ -144,10 +145,32 @@ class MetalBufferObject : public HwBufferObject { MetalBuffer buffer; }; +struct MetalVertexBufferInfo : public HwVertexBufferInfo { + MetalVertexBufferInfo(MetalContext& context, + uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes); + + // This struct is used to create the pipeline description to describe vertex assembly. + VertexDescription vertexDescription = {}; + + struct Entry { + uint8_t sourceBufferIndex = 0; + uint8_t stride = 0; + // maps to -> + uint8_t bufferArgumentIndex = 0; + + Entry(uint8_t sourceBufferIndex, uint8_t stride, uint8_t bufferArgumentIndex) + : sourceBufferIndex(sourceBufferIndex), + stride(stride), + bufferArgumentIndex(bufferArgumentIndex) {} + }; + utils::FixedCapacityVector bufferMapping; +}; + struct MetalVertexBuffer : public HwVertexBuffer { - MetalVertexBuffer(MetalContext& context, uint8_t bufferCount, uint8_t attributeCount, - uint32_t vertexCount, AttributeArray const& attributes); + MetalVertexBuffer(MetalContext& context, + uint32_t vertexCount, uint32_t bufferCount, Handle vbih); + Handle vbih; utils::FixedCapacityVector buffers; }; @@ -159,27 +182,30 @@ struct MetalIndexBuffer : public HwIndexBuffer { }; struct MetalRenderPrimitive : public HwRenderPrimitive { - void setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer); + MetalRenderPrimitive(); + void setBuffers(MetalVertexBufferInfo const* const vbi, + MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer); // The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak". // The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive. MetalVertexBuffer* vertexBuffer = nullptr; MetalIndexBuffer* indexBuffer = nullptr; - - // This struct is used to create the pipeline description to describe vertex assembly. - VertexDescription vertexDescription = {}; }; -struct MetalProgram : public HwProgram { - MetalProgram(id device, const Program& program) noexcept; +class MetalProgram : public HwProgram { +public: + MetalProgram(MetalContext& context, Program&& program) noexcept; - id vertexFunction; - id fragmentFunction; - id computeFunction; + const MetalShaderCompiler::MetalFunctionBundle& getFunctions(); + const Program::SamplerGroupInfo& getSamplerGroupInfo() { return samplerGroupInfo; } - Program::SamplerGroupInfo samplerGroupInfo; +private: + void initialize(); - bool isValid = false; + Program::SamplerGroupInfo samplerGroupInfo; + MetalContext& mContext; + MetalShaderCompiler::MetalFunctionBundle mFunctionBundle; + MetalShaderCompiler::program_token_t mToken; }; struct PixelBufferShape { @@ -228,8 +254,8 @@ class MetalTexture : public HwTexture { // - using the texture as a render target attachment // - calling setMinMaxLevels // A texture's available mips are consistent throughout a render pass. - void setLodRange(uint32_t minLevel, uint32_t maxLevel); - void extendLodRangeTo(uint32_t level); + void setLodRange(uint16_t minLevel, uint16_t maxLevel); + void extendLodRangeTo(uint16_t level); static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format); @@ -243,6 +269,26 @@ class MetalTexture : public HwTexture { MTLPixelFormat devicePixelFormat; + // Frees memory associated with this texture and marks it as "terminated". + // Used to track "use after free" scenario. + void terminate() noexcept; + bool isTerminated() const noexcept { return terminated; } + inline void checkUseAfterFree(const char* samplerGroupDebugName, size_t textureIndex) const { + if (UTILS_LIKELY(!isTerminated())) { + return; + } + NSString* reason = + [NSString stringWithFormat: + @"Filament Metal texture use after free, sampler group = " + @"%s, texture index = %zu", + samplerGroupDebugName, textureIndex]; + NSException* useAfterFreeException = + [NSException exceptionWithName:@"MetalTextureUseAfterFree" + reason:reason + userInfo:nil]; + [useAfterFreeException raise]; + } + private: void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice, PixelBufferDescriptor const& data) noexcept; @@ -259,14 +305,17 @@ class MetalTexture : public HwTexture { id swizzledTextureView = nil; id lodTextureView = nil; - uint32_t minLod = UINT_MAX; - uint32_t maxLod = 0; + uint16_t minLod = std::numeric_limits::max(); + uint16_t maxLod = 0; + + bool terminated = false; }; class MetalSamplerGroup : public HwSamplerGroup { public: - explicit MetalSamplerGroup(size_t size) noexcept + explicit MetalSamplerGroup(size_t size, utils::FixedSizeString<32> name) noexcept : size(size), + debugName(name), textureHandles(size, Handle()), textures(size, nil), samplers(size, nil) {} @@ -276,12 +325,10 @@ class MetalSamplerGroup : public HwSamplerGroup { textureHandles[index] = th; } -#ifndef NDEBUG // This method is only used for debugging, to ensure all texture handles are alive. const auto& getTextureHandles() const { return textureHandles; } -#endif // Encode a MTLTexture into this SamplerGroup at the given index. inline void setFinalizedTexture(size_t index, id t) { @@ -327,6 +374,7 @@ class MetalSamplerGroup : public HwSamplerGroup { void useResources(id renderPassEncoder); size_t size; + utils::FixedSizeString<32> debugName; public: diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 595ec6f9cb4..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 @@ -39,7 +40,15 @@ namespace backend { static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { - NSUInteger u = 0; + NSUInteger u = MTLTextureUsageUnknown; + + if (any(usage & TextureUsage::SAMPLEABLE)) { + u |= MTLTextureUsageShaderRead; + } + if (any(usage & TextureUsage::UPLOADABLE)) { + // This is only needed because of the slowpath is MetalBlitter + u |= MTLTextureUsageRenderTarget; + } if (any(usage & TextureUsage::COLOR_ATTACHMENT)) { u |= MTLTextureUsageRenderTarget; } @@ -49,9 +58,13 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) { u |= MTLTextureUsageRenderTarget; } - - // All textures can be blitted from, so they must have the UsageShaderRead flag. - u |= MTLTextureUsageShaderRead; + if (any(usage & TextureUsage::BLIT_DST)) { + // This is only needed because of the slowpath is MetalBlitter + u |= MTLTextureUsageRenderTarget; + } + if (any(usage & TextureUsage::BLIT_SRC)) { + u |= MTLTextureUsageShaderRead; + } return MTLTextureUsage(u); } @@ -233,13 +246,53 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { } } -void presentDrawable(bool presentFrame, void* user) { - // CFBridgingRelease here is used to balance the CFBridgingRetain inside of acquireDrawable. - id drawable = (id) CFBridgingRelease(user); - if (presentFrame) { - [drawable present]; +#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD +#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1 +#endif + +class PresentDrawableData { +public: + PresentDrawableData() = delete; + PresentDrawableData(const PresentDrawableData&) = delete; + PresentDrawableData& operator=(const PresentDrawableData&) = delete; + + static PresentDrawableData* create(id drawable, MetalDriver* driver) { + assert_invariant(driver); + return new PresentDrawableData(drawable, driver); + } + + static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) { + if (shouldPresent) { + [that->mDrawable present]; + } + +#if FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD == 1 + // mDrawable is acquired on the driver thread. Typically, we would release this object on + // the same thread, but after receiving consistent crash reports from within + // [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread. + dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); }); +#else + that->mDriver->runAtNextTick([that]() { cleanupAndDestroy(that); }); +#endif + } + +private: + PresentDrawableData(id drawable, MetalDriver* driver) + : mDrawable(drawable), mDriver(driver) {} + + static void cleanupAndDestroy(PresentDrawableData *that) { + that->mDrawable = nil; + that->mDriver = nullptr; + delete that; } - // The drawable will be released here when the "drawable" variable goes out of scope. + + id mDrawable; + MetalDriver* mDriver = nullptr; +}; + +void presentDrawable(bool presentFrame, void* user) { + auto* presentDrawableData = static_cast(user); + PresentDrawableData::maybePresentAndDestroyAsync(presentDrawableData, presentFrame); } void MetalSwapChain::scheduleFrameScheduledCallback() { @@ -248,19 +301,16 @@ void presentDrawable(bool presentFrame, void* user) { } assert_invariant(drawable); - FrameScheduledCallback callback = frameScheduledCallback; - // This block strongly captures drawable to keep it alive until the handler executes. - // We cannot simply reference this->drawable inside the block because the block would then only - // capture the _this_ pointer (MetalSwapChain*) instead of the drawable. - id d = drawable; + + // Destroy this by calling maybePresentAndDestroyAsync() later. + auto* presentData = PresentDrawableData::create(drawable, context.driver); + + FrameScheduledCallback userCallback = frameScheduledCallback; void* userData = frameScheduledUserData; + [getPendingCommandBuffer(&context) addScheduledHandler:^(id cb) { - // CFBridgingRetain is used here to give the drawable a +1 retain count before - // casting it to a void*. - PresentCallable callable(presentDrawable, (void*) CFBridgingRetain(d)); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - callback(callable, userData); - }); + PresentCallable callable(presentDrawable, static_cast(presentData)); + userCallback(callable, userData); }]; } @@ -291,133 +341,119 @@ void presentDrawable(bool presentFrame, void* user) { buffer.copyIntoBufferUnsynchronized(data, size, byteOffset); } -MetalVertexBuffer::MetalVertexBuffer(MetalContext& context, uint8_t bufferCount, - uint8_t attributeCount, uint32_t vertexCount, AttributeArray const& attributes) - : HwVertexBuffer(bufferCount, attributeCount, vertexCount, attributes), buffers(bufferCount, nullptr) {} +MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount, + uint8_t attributeCount, AttributeArray const& attributes) + : HwVertexBufferInfo(bufferCount, attributeCount), + bufferMapping(utils::FixedCapacityVector::with_capacity(MAX_VERTEX_BUFFER_COUNT)) { -MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize, - uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount), - buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { } + const size_t maxAttributeCount = attributes.size(); -void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* - indexBuffer) { - this->vertexBuffer = vertexBuffer; - this->indexBuffer = indexBuffer; + auto& mapping = bufferMapping; + mapping.clear(); + vertexDescription = {}; - const size_t attributeCount = vertexBuffer->attributes.size(); + // Set the layout for the zero buffer, which unused attributes are mapped to. + vertexDescription.layouts[ZERO_VERTEX_BUFFER_LOGICAL_INDEX] = { + .step = MTLVertexStepFunctionConstant, .stride = 16 + }; - vertexDescription = {}; + // Here we map each source buffer to a Metal buffer argument. + // Each attribute has a source buffer, offset, and stride. + // Two source buffers with the same index and stride can share the same Metal buffer argument + // index. + // + // The source buffer is the buffer index that the Filament client sets. + // * source buffer + // .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) + // .attribute(VertexAttribute::UV, 0, VertexBuffer::AttributeType::HALF2, 8, 12) + // .attribute(VertexAttribute::COLOR, 1, VertexBuffer::AttributeType::UBYTE4, 0, 4) + + auto allocateOrGetBufferArgumentIndex = + [&mapping, currentBufferArgumentIndex = USER_VERTEX_BUFFER_BINDING_START, this]( + auto sourceBuffer, auto sourceBufferStride) mutable -> uint8_t { + auto match = [&](const auto& e) { + return e.sourceBufferIndex == sourceBuffer && e.stride == sourceBufferStride; + }; + if (auto it = std::find_if(mapping.begin(), mapping.end(), match); it != mapping.end()) { + return it->bufferArgumentIndex; + } else { + auto bufferArgumentIndex = currentBufferArgumentIndex++; + mapping.emplace_back(sourceBuffer, sourceBufferStride, bufferArgumentIndex); + vertexDescription.layouts[bufferArgumentIndex] = { + .step = MTLVertexStepFunctionPerVertex, .stride = sourceBufferStride + }; + return bufferArgumentIndex; + } + }; - // Each attribute gets its own vertex buffer, starting at logical buffer 1. - uint32_t bufferIndex = 1; - for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) { - const auto& attribute = vertexBuffer->attributes[attributeIndex]; - if (attribute.buffer == Attribute::BUFFER_UNUSED) { - const uint8_t flags = attribute.flags; - const MTLVertexFormat format = (flags & Attribute::FLAG_INTEGER_TARGET) ? - MTLVertexFormatUInt4 : MTLVertexFormatFloat4; + for (uint32_t attributeIndex = 0; attributeIndex < maxAttributeCount; attributeIndex++) { + const auto& attribute = attributes[attributeIndex]; - // If the attribute is not enabled, bind it to the zero buffer. It's a Metal error for a - // shader to read from missing vertex attributes. + // If the attribute is unused, bind it to the zero buffer. It's a Metal error for a shader + // to read from missing vertex attributes. + if (attribute.buffer == Attribute::BUFFER_UNUSED) { + const MTLVertexFormat format = (attribute.flags & Attribute::FLAG_INTEGER_TARGET) + ? MTLVertexFormatUInt4 + : MTLVertexFormatFloat4; vertexDescription.attributes[attributeIndex] = { - .format = format, - .buffer = ZERO_VERTEX_BUFFER_LOGICAL_INDEX, - .offset = 0 - }; - vertexDescription.layouts[ZERO_VERTEX_BUFFER_LOGICAL_INDEX] = { - .step = MTLVertexStepFunctionConstant, - .stride = 16 + .format = format, .buffer = ZERO_VERTEX_BUFFER_LOGICAL_INDEX, .offset = 0 }; continue; } + // Map the source buffer and stride of this attribute to a Metal buffer argument. + auto bufferArgumentIndex = + allocateOrGetBufferArgumentIndex(attribute.buffer, attribute.stride); + vertexDescription.attributes[attributeIndex] = { - .format = getMetalFormat(attribute.type, - attribute.flags & Attribute::FLAG_NORMALIZED), - .buffer = bufferIndex, - .offset = 0 - }; - vertexDescription.layouts[bufferIndex] = { - .step = MTLVertexStepFunctionPerVertex, - .stride = attribute.stride + .format = getMetalFormat( + attribute.type, attribute.flags & Attribute::FLAG_NORMALIZED), + .buffer = uint32_t(bufferArgumentIndex), + .offset = attribute.offset }; - - bufferIndex++; - }; + } } -MetalProgram::MetalProgram(id device, const Program& program) noexcept - : HwProgram(program.getName()), vertexFunction(nil), fragmentFunction(nil), - computeFunction(nil), isValid(false) { - - using MetalFunctionPtr = __strong id*; - - static_assert(Program::SHADER_TYPE_COUNT == 3, "Only vertex, fragment, and/or compute shaders expected."); - MetalFunctionPtr shaderFunctions[3] = { &vertexFunction, &fragmentFunction, &computeFunction }; - - const auto& sources = program.getShadersSource(); - for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) { - const auto& source = sources[i]; - // It's okay for some shaders to be empty, they shouldn't be used in any draw calls. - if (source.empty()) { - continue; - } - - assert_invariant( source[source.size() - 1] == '\0' ); - - // the shader string is null terminated and the length includes the null character - NSString* objcSource = [[NSString alloc] initWithBytes:source.data() - length:source.size() - 1 - encoding:NSUTF8StringEncoding]; - NSError* error = nil; - // When options is nil, Metal uses the most recent language version available. - id library = [device newLibraryWithSource:objcSource - options:nil - error:&error]; - if (library == nil) { - if (error) { - auto description = - [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]; - utils::slog.w << description << utils::io::endl; - } - PANIC_LOG("Failed to compile Metal program."); - return; - } +MetalVertexBuffer::MetalVertexBuffer(MetalContext& context, + uint32_t vertexCount, uint32_t bufferCount, Handle vbih) + : HwVertexBuffer(vertexCount), vbih(vbih), buffers(bufferCount, nullptr) { +} - MTLFunctionConstantValues* constants = [MTLFunctionConstantValues new]; - auto const& specializationConstants = program.getSpecializationConstants(); - for (auto const& sc : specializationConstants) { - const std::array types{ - MTLDataTypeInt, MTLDataTypeFloat, MTLDataTypeBool }; - std::visit([&sc, constants, type = types[sc.value.index()]](auto&& arg) { - [constants setConstantValue:&arg - type:type - atIndex:sc.id]; - }, sc.value); - } +MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize, + uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount), + buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { } - id function = [library newFunctionWithName:@"main0" - constantValues:constants - error:&error]; - if (!program.getName().empty()) { - function.label = @(program.getName().c_str()); - } - assert_invariant(function); - *shaderFunctions[i] = function; - } +MetalRenderPrimitive::MetalRenderPrimitive() { +} - UTILS_UNUSED_IN_RELEASE const bool isRasterizationProgram = - vertexFunction != nil && fragmentFunction != nil; - UTILS_UNUSED_IN_RELEASE const bool isComputeProgram = computeFunction != nil; - // The program must be either a rasterization program XOR a compute program. - assert_invariant(isRasterizationProgram != isComputeProgram); +void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi, + MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer) { + this->vertexBuffer = vertexBuffer; + this->indexBuffer = indexBuffer; +} - // All stages of the program have compiled successfully, this is a valid program. - isValid = true; +MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept + : HwProgram(program.getName()), mContext(context) { // Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to // the appropriate stage(s). samplerGroupInfo = program.getSamplerGroupInfo(); + + mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program)); + assert_invariant(mToken); +} + +const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctions() { + initialize(); + return mFunctionBundle; +} + +void MetalProgram::initialize() { + if (!mToken) { + return; + } + mFunctionBundle = mContext.shaderCompiler->getProgram(mToken); + assert_invariant(!mToken); } MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, @@ -535,6 +571,15 @@ void presentDrawable(bool presentFrame, void* user) { setLodRange(0, levels - 1); } +void MetalTexture::terminate() noexcept { + texture = nil; + swizzledTextureView = nil; + lodTextureView = nil; + msaaSidecar = nil; + externalImage.set(nullptr); + terminated = true; +} + MetalTexture::~MetalTexture() { externalImage.set(nullptr); } @@ -726,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 @@ -768,6 +813,7 @@ void presentDrawable(bool presentFrame, void* user) { #endif id stagingTexture = [context.device newTextureWithDescriptor:descriptor]; + // FIXME? Why is this not just `MTLRegion sourceRegion = region;` ? MTLRegion sourceRegion = MTLRegionMake3D(0, 0, 0, region.size.width, region.size.height, region.size.depth); [stagingTexture replaceRegion:sourceRegion @@ -794,27 +840,27 @@ void presentDrawable(bool presentFrame, void* user) { slices:NSMakeRange(0, slices)]; } - MetalBlitter::BlitArgs args; + MetalBlitter::BlitArgs args{}; args.filter = SamplerMagFilter::NEAREST; args.source.level = 0; args.source.slice = 0; args.source.region = sourceRegion; + args.source.texture = stagingTexture; args.destination.level = level; args.destination.slice = slice; args.destination.region = region; - args.source.color = stagingTexture; - args.destination.color = destinationTexture; + args.destination.texture = destinationTexture; context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit"); } -void MetalTexture::extendLodRangeTo(uint32_t level) { +void MetalTexture::extendLodRangeTo(uint16_t level) { assert_invariant(!isInRenderPass(&context)); minLod = std::min(minLod, level); maxLod = std::max(maxLod, level); lodTextureView = nil; } -void MetalTexture::setLodRange(uint32_t min, uint32_t max) { +void MetalTexture::setLodRange(uint16_t min, uint16_t max) { assert_invariant(!isInRenderPass(&context)); assert_invariant(min <= max); minLod = min; diff --git a/filament/backend/src/metal/MetalShaderCompiler.h b/filament/backend/src/metal/MetalShaderCompiler.h new file mode 100644 index 00000000000..f0b827ad6ac --- /dev/null +++ b/filament/backend/src/metal/MetalShaderCompiler.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_METAL_METALSHADERCOMPILER_H +#define TNT_FILAMENT_BACKEND_METAL_METALSHADERCOMPILER_H + +#include "CompilerThreadPool.h" + +#include "CallbackManager.h" + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace filament::backend { + +class MetalDriver; + +class MetalShaderCompiler { + struct MetalProgramToken; + +public: + enum class Mode { + SYNCHRONOUS, // synchronous shader compilation + ASYNCHRONOUS // asynchronous shader compilation + }; + + class MetalFunctionBundle { + public: + using Raster = std::tuple, id>; + using Compute = id; + using Error = std::tuple; // error message, Program name + struct None {}; + + MetalFunctionBundle() : mPrograms{None{}} {} + + explicit operator bool() const { return isValid(); } + + bool isValid() const noexcept { + return std::holds_alternative(mPrograms) || + std::holds_alternative(mPrograms); + } + + /** + * Throws an NSException if this MetalFunctionBundle either contains an error or is empty. + * + * If this MetalFunctionBundle contains an error, will throw a MetalCompilationFailure + * NSException with the error string and program name passed to + * MetalFunctionBundle::error(NSString*, NSString*). + * + * If this MetalFunctionBundle is empty, will throw a MetalEmptyFunctionBundle NSException. + */ + void validate() const; + + Raster getRasterFunctions() const { + assert_invariant(std::holds_alternative(mPrograms)); + return std::get(mPrograms); + } + + Compute getComputeFunction() const { + assert_invariant(std::holds_alternative(mPrograms)); + return std::get(mPrograms); + } + + static MetalFunctionBundle none() { + return MetalFunctionBundle(None{}); + } + + static MetalFunctionBundle raster(id fragment, id vertex) { + assert_invariant(fragment && vertex); + assert_invariant(fragment.functionType == MTLFunctionTypeFragment); + assert_invariant(vertex.functionType == MTLFunctionTypeVertex); + return MetalFunctionBundle(Raster{fragment, vertex}); + } + + static MetalFunctionBundle compute(id compute) { + assert_invariant(compute); + assert_invariant(compute.functionType == MTLFunctionTypeKernel); + return MetalFunctionBundle(Compute{compute}); + } + + static MetalFunctionBundle error(NSString* errorMessage, NSString* programName) { + return MetalFunctionBundle(Error{errorMessage, programName}); + } + + private: + MetalFunctionBundle(None&& t) : mPrograms(std::move(t)) {} + MetalFunctionBundle(Raster&& t) : mPrograms(std::move(t)) {} + MetalFunctionBundle(Compute&& t) : mPrograms(std::move(t)) {} + MetalFunctionBundle(Error&& t) : mPrograms(std::move(t)) {} + + std::variant mPrograms; + }; + + using program_token_t = std::shared_ptr; + + explicit MetalShaderCompiler(id device, MetalDriver& driver, Mode mode); + + MetalShaderCompiler(MetalShaderCompiler const& rhs) = delete; + MetalShaderCompiler(MetalShaderCompiler&& rhs) = delete; + MetalShaderCompiler& operator=(MetalShaderCompiler const& rhs) = delete; + MetalShaderCompiler& operator=(MetalShaderCompiler&& rhs) = delete; + + void init() noexcept; + void terminate() noexcept; + + bool isParallelShaderCompileSupported() const noexcept; + + // Creates a program, either synchronously or asynchronously, depending on the Mode + // MetalShaderCompiler was constructed with. + program_token_t createProgram(utils::CString const& name, Program&& program); + + // Returns the functions, blocking if necessary. The Token is destroyed and becomes invalid. + MetalFunctionBundle getProgram(program_token_t& token); + + void notifyWhenAllProgramsAreReady( + CallbackHandler* handler, CallbackHandler::Callback callback, void* user); + +private: + static MetalFunctionBundle compileProgram(const Program& program, id device); + + CompilerThreadPool mCompilerThreadPool; + id mDevice; + CallbackManager mCallbackManager; + Mode mMode; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_METAL_METALSHADERCOMPILER_H diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm new file mode 100644 index 00000000000..7741ae54489 --- /dev/null +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2023 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 "MetalShaderCompiler.h" + +#include "MetalDriver.h" + +#include + +#include +#include + +#include + +namespace filament::backend { + +using namespace utils; + +struct MetalShaderCompiler::MetalProgramToken : ProgramToken { + + MetalProgramToken(MetalShaderCompiler& compiler) noexcept + : compiler(compiler) { + } + ~MetalProgramToken() override; + + void set(MetalFunctionBundle p) noexcept { + std::unique_lock const l(lock); + std::swap(program, p); + signaled = true; + cond.notify_one(); + } + + MetalFunctionBundle get() const noexcept { + std::unique_lock l(lock); + cond.wait(l, [this](){ return signaled; }); + return program; + } + + void wait() const noexcept { + std::unique_lock l(lock); + cond.wait(l, [this]() { return signaled; }); + } + + bool isReady() const noexcept { + std::unique_lock l(lock); + using namespace std::chrono_literals; + return cond.wait_for(l, 0s, [this]() { return signaled; }); + } + + MetalShaderCompiler& compiler; + CallbackManager::Handle handle{}; + MetalFunctionBundle program{}; + mutable utils::Mutex lock; + mutable utils::Condition cond; + bool signaled = false; +}; + +MetalShaderCompiler::MetalProgramToken::~MetalProgramToken() = default; + +MetalShaderCompiler::MetalShaderCompiler(id device, MetalDriver& driver, Mode mode) + : mDevice(device), + mCallbackManager(driver), + mMode(mode) { + +} + +void MetalShaderCompiler::init() noexcept { + const uint32_t poolSize = 1; + if (mMode == Mode::ASYNCHRONOUS) { + mCompilerThreadPool.init(poolSize, []() {}, []() {}); + } +} + +void MetalShaderCompiler::terminate() noexcept { + if (mMode == Mode::ASYNCHRONOUS) { + mCompilerThreadPool.terminate(); + } + mCallbackManager.terminate(); +} + +bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept { + return mMode == Mode::ASYNCHRONOUS; +} + +/* static */ MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::compileProgram( + const Program& program, id device) { + std::array, Program::SHADER_TYPE_COUNT> functions = { nil }; + const auto& sources = program.getShadersSource(); + for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) { + const auto& source = sources[i]; + // It's okay for some shaders to be empty, they shouldn't be used in any draw calls. + if (source.empty()) { + continue; + } + + assert_invariant(source[source.size() - 1] == '\0'); + + // the shader string is null terminated and the length includes the null character + NSString* objcSource = [[NSString alloc] initWithBytes:source.data() + length:source.size() - 1 + encoding:NSUTF8StringEncoding]; + + // By default, Metal uses the most recent language version. + MTLCompileOptions* options = [MTLCompileOptions new]; + + // Disable Fast Math optimizations. + // This ensures that operations adhere to IEEE standards for floating-point arithmetic, + // which is crucial for half precision floats in scenarios where fast math optimizations + // lead to inaccuracies, such as in handling special values like NaN or Infinity. + options.fastMathEnabled = NO; + + NSError* error = nil; + id library = [device newLibraryWithSource:objcSource + options:options + error:&error]; + if (library == nil) { + NSString* errorMessage = @"unknown error"; + if (error) { + auto description = + [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]; + utils::slog.w << description << utils::io::endl; + errorMessage = error.localizedDescription; + } + PANIC_LOG("Failed to compile Metal program."); + NSString* programName = [NSString stringWithFormat:@"%s", program.getName().c_str_safe()]; + return MetalFunctionBundle::error(errorMessage, programName); + } + + MTLFunctionConstantValues* constants = [MTLFunctionConstantValues new]; + auto const& specializationConstants = program.getSpecializationConstants(); + for (auto const& sc : specializationConstants) { + const std::array types{ + MTLDataTypeInt, MTLDataTypeFloat, MTLDataTypeBool }; + std::visit([&sc, constants, type = types[sc.value.index()]](auto&& arg) { + [constants setConstantValue:&arg + type:type + atIndex:sc.id]; + }, sc.value); + } + + id function = [library newFunctionWithName:@"main0" + constantValues:constants + error:&error]; + if (!program.getName().empty()) { + function.label = @(program.getName().c_str()); + } + assert_invariant(function); + functions[i] = function; + } + + static_assert(Program::SHADER_TYPE_COUNT == 3, + "Only vertex, fragment, and/or compute shaders expected."); + id vertexFunction = functions[0]; + id fragmentFunction = functions[1]; + id computeFunction = functions[2]; + const bool isRasterizationProgram = vertexFunction != nil && fragmentFunction != nil; + const bool isComputeProgram = computeFunction != nil; + // The program must be either a rasterization program XOR a compute program. + assert_invariant(isRasterizationProgram != isComputeProgram); + + if (isRasterizationProgram) { + return MetalFunctionBundle::raster(fragmentFunction, vertexFunction); + } + + if (isComputeProgram) { + return MetalFunctionBundle::compute(computeFunction); + } + + // Should never reach here. + return MetalFunctionBundle::none(); +} + +MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram( + CString const& name, Program&& program) { + auto token = std::make_shared(*this); + + token->handle = mCallbackManager.get(); + + switch (mMode) { + case Mode::ASYNCHRONOUS: { + CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); + mCompilerThreadPool.queue(priorityQueue, token, + [this, name, device = mDevice, program = std::move(program), token]() { + MetalFunctionBundle compiledProgram = compileProgram(program, device); + token->set(compiledProgram); + mCallbackManager.put(token->handle); + }); + + break; + } + + case Mode::SYNCHRONOUS: { + MetalFunctionBundle compiledProgram = compileProgram(program, mDevice); + token->set(compiledProgram); + mCallbackManager.put(token->handle); + break; + } + } + + return token; +} + +MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::getProgram(program_token_t& token) { + assert_invariant(token); + + if (mMode == Mode::ASYNCHRONOUS) { + if (!token->isReady()) { + auto job = mCompilerThreadPool.dequeue(token); + if (job) { + job(); + } + } + } + + // The job isn't guaranteed to have finished yet. We may have failed to dequeue it above, + // which means it's currently running. In that case get() will block until it finishes. + + MetalShaderCompiler::MetalFunctionBundle program = token->get(); + token = nullptr; + return program; +} + +void MetalShaderCompiler::notifyWhenAllProgramsAreReady( + CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { + mCallbackManager.setCallback(handler, callback, user); +} + +UTILS_NOINLINE +void MetalShaderCompiler::MetalFunctionBundle::validate() const { + if (UTILS_UNLIKELY(std::holds_alternative(mPrograms))) { + auto [errorMessage, programName] = std::get(mPrograms); + NSString* reason = + [NSString stringWithFormat: + @"Attempting to draw with an id that failed to compile.\n" + @"Program: %@\n" + @"%@", programName, errorMessage]; + [[NSException exceptionWithName:@"MetalCompilationFailure" + reason:reason + userInfo:nil] raise]; + } else if (UTILS_UNLIKELY(std::holds_alternative(mPrograms))) { + NSString* reason = @"Attempting to draw with an empty id."; + [[NSException exceptionWithName:@"MetalEmptyFunctionBundle" + reason:reason + userInfo:nil] raise]; + } +} + +} // namespace filament::backend diff --git a/filament/backend/src/metal/MetalState.mm b/filament/backend/src/metal/MetalState.mm index 2b8a02e7bf8..2b2e5bd65b2 100644 --- a/filament/backend/src/metal/MetalState.mm +++ b/filament/backend/src/metal/MetalState.mm @@ -18,6 +18,8 @@ #include "MetalEnums.h" +#include + namespace filament { namespace backend { diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 3d1a9cdc327..7a150a3e74a 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -77,6 +77,9 @@ void NoopDriver::finish(int) { void NoopDriver::destroyRenderPrimitive(Handle rph) { } +void NoopDriver::destroyVertexBufferInfo(Handle vbih) { +} + void NoopDriver::destroyVertexBuffer(Handle vbh) { } @@ -174,7 +177,11 @@ bool NoopDriver::isSRGBSwapChainSupported() { return false; } -bool NoopDriver::isStereoSupported() { +bool NoopDriver::isProtectedContentSupported() { + return false; +} + +bool NoopDriver::isStereoSupported(backend::StereoscopicType) { return false; } @@ -182,6 +189,14 @@ bool NoopDriver::isParallelShaderCompileSupported() { return false; } +bool NoopDriver::isDepthStencilResolveSupported() { + return true; +} + +bool NoopDriver::isProtectedTexturesSupported() { + return true; +} + bool NoopDriver::isWorkaroundNeeded(Workaround) { return false; } @@ -237,8 +252,8 @@ void NoopDriver::update3DImage(Handle th, void NoopDriver::setupExternalImage(void* image) { } -bool NoopDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { - return false; +TimerQueryResult NoopDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { + return TimerQueryResult::ERROR; } void NoopDriver::setExternalImage(Handle th, void* image) { @@ -252,10 +267,6 @@ void NoopDriver::setExternalStream(Handle th, Handle sh) { void NoopDriver::generateMipmaps(Handle th) { } -bool NoopDriver::canGenerateMipmaps() { - return true; -} - void NoopDriver::updateSamplerGroup(Handle sbh, BufferDescriptor&& data) { scheduleDestroy(std::move(data)); @@ -322,19 +333,43 @@ void NoopDriver::readBufferSubData(backend::BufferObjectHandle boh, scheduleDestroy(std::move(p)); } -void NoopDriver::blit(TargetBufferFlags buffers, +void NoopDriver::blitDEPRECATED(TargetBufferFlags buffers, Handle dst, Viewport dstRect, Handle src, Viewport srcRect, SamplerMagFilter filter) { } +void NoopDriver::resolve( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, + Handle src, uint8_t dstLevel, uint8_t dstLayer) { +} + +void NoopDriver::blit( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin, + Handle src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin, + math::uint2 size) { +} + +void NoopDriver::bindPipeline(PipelineState pipelineState) { +} + +void NoopDriver::bindRenderPrimitive(Handle rph) { +} + +void NoopDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { +} + void NoopDriver::draw(PipelineState pipelineState, Handle rph, - uint32_t instanceCount) { + uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { } void NoopDriver::dispatchCompute(Handle program, math::uint3 workGroupCount) { } +void NoopDriver::scissor( + Viewport scissor) { +} + void NoopDriver::beginTimerQuery(Handle tqh) { } diff --git a/filament/backend/src/opengl/GLBufferObject.h b/filament/backend/src/opengl/GLBufferObject.h new file mode 100644 index 00000000000..e40e19bd355 --- /dev/null +++ b/filament/backend/src/opengl/GLBufferObject.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H +#define TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H + +#include "DriverBase.h" + +#include "gl_headers.h" + +#include + +#include + +namespace filament::backend { + +struct GLBufferObject : public HwBufferObject { + using HwBufferObject::HwBufferObject; + GLBufferObject(uint32_t size, + BufferObjectBinding bindingType, BufferUsage usage) noexcept + : HwBufferObject(size), usage(usage), bindingType(bindingType) { + } + + struct { + GLuint id; + union { + GLenum binding; + void* buffer; + }; + } gl; + BufferUsage usage; + BufferObjectBinding bindingType; + uint16_t age = 0; +}; + +} // namespace filament::backend + +#endif //TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H diff --git a/filament/backend/src/opengl/GLTexture.h b/filament/backend/src/opengl/GLTexture.h new file mode 100644 index 00000000000..91aadfc36af --- /dev/null +++ b/filament/backend/src/opengl/GLTexture.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H +#define TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H + +#include "DriverBase.h" + +#include "gl_headers.h" + +#include + +#include + +namespace filament::backend { + +struct GLTexture : public HwTexture { + using HwTexture::HwTexture; + struct GL { + GL() noexcept : imported(false), sidecarSamples(1), reserved(0) {} + GLuint id = 0; // texture or renderbuffer id + GLenum target = 0; + GLenum internalFormat = 0; + GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer + + // texture parameters go here too + GLfloat anisotropy = 1.0; + int8_t baseLevel = 127; + int8_t maxLevel = -1; + uint8_t targetIndex = 0; // optimization: index corresponding to target + bool imported : 1; + uint8_t sidecarSamples : 4; + uint8_t reserved : 3; + } gl; + + OpenGLPlatform::ExternalTexture* externalTexture = nullptr; +}; + + +} // namespace filament::backend + +#endif //TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H diff --git a/filament/backend/src/opengl/GLUtils.h b/filament/backend/src/opengl/GLUtils.h index 0c7bca140ee..dc9df7871a7 100644 --- a/filament/backend/src/opengl/GLUtils.h +++ b/filament/backend/src/opengl/GLUtils.h @@ -373,16 +373,19 @@ constexpr inline GLenum getCullingMode(CullingMode mode) noexcept { constexpr inline std::pair textureFormatToFormatAndType( TextureFormat format) noexcept { switch (format) { - case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE }; - case TextureFormat::RGBA8: return { GL_RGBA, GL_UNSIGNED_BYTE }; - case TextureFormat::RGB565: return { GL_RGB, GL_UNSIGNED_SHORT_5_6_5 }; - case TextureFormat::RGB5_A1: return { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 }; - case TextureFormat::RGBA4: return { GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 }; - case TextureFormat::DEPTH16: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }; - case TextureFormat::DEPTH24: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; + case TextureFormat::R8: return { 0x1909 /*GL_LUMINANCE*/, GL_UNSIGNED_BYTE }; + case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE }; + case TextureFormat::SRGB8: return { GL_RGB, GL_UNSIGNED_BYTE }; + case TextureFormat::RGBA8: return { GL_RGBA, GL_UNSIGNED_BYTE }; + case TextureFormat::SRGB8_A8: return { GL_RGBA, GL_UNSIGNED_BYTE }; + case TextureFormat::RGB565: return { GL_RGB, GL_UNSIGNED_SHORT_5_6_5 }; + case TextureFormat::RGB5_A1: return { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 }; + case TextureFormat::RGBA4: return { GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 }; + case TextureFormat::DEPTH16: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }; + case TextureFormat::DEPTH24: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; case TextureFormat::DEPTH24_STENCIL8: - return { GL_DEPTH24_STENCIL8, GL_UNSIGNED_INT_24_8 }; - default: return { GL_NONE, GL_NONE }; + return { GL_DEPTH24_STENCIL8, GL_UNSIGNED_INT_24_8 }; + default: return { GL_NONE, GL_NONE }; } } diff --git a/filament/backend/src/opengl/OpenGLBlobCache.cpp b/filament/backend/src/opengl/OpenGLBlobCache.cpp index 48155dc23f3..f9e68384da5 100644 --- a/filament/backend/src/opengl/OpenGLBlobCache.cpp +++ b/filament/backend/src/opengl/OpenGLBlobCache.cpp @@ -16,6 +16,8 @@ #include "OpenGLBlobCache.h" +#include "OpenGLContext.h" + #include #include @@ -28,17 +30,18 @@ struct OpenGLBlobCache::Blob { char data[]; }; +OpenGLBlobCache::OpenGLBlobCache(OpenGLContext& gl) noexcept + : mCachingSupported(gl.gets.num_program_binary_formats >= 1) { +} + GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform, - Program const& program) noexcept { + Program const& program) const noexcept { SYSTRACE_CALL(); - - if (!platform.hasBlobFunc()) { + if (!mCachingSupported || !platform.hasRetrieveBlobFunc()) { // the key is never updated in that case return 0; } - SYSTRACE_CONTEXT(); - GLuint programId = 0; #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 @@ -64,8 +67,10 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform, programId = glCreateProgram(); - SYSTRACE_NAME("glProgramBinary"); - glProgramBinary(programId, blob->format, blob->data, programBinarySize); + { // scope for systrace + SYSTRACE_NAME("glProgramBinary"); + glProgramBinary(programId, blob->format, blob->data, programBinarySize); + } if (UTILS_UNLIKELY(glGetError() != GL_NO_ERROR)) { // glProgramBinary can fail if for instance the driver has been updated @@ -85,19 +90,28 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform, void OpenGLBlobCache::insert(Platform& platform, BlobCacheKey const& key, GLuint program) noexcept { -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 SYSTRACE_CALL(); - if (platform.hasBlobFunc()) { - SYSTRACE_CONTEXT(); - GLenum format; - GLint programBinarySize; + if (!mCachingSupported || !platform.hasInsertBlobFunc()) { + // the key is never updated in that case + return; + } + +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + GLenum format; + GLint programBinarySize = 0; + { // scope for systrace SYSTRACE_NAME("glGetProgramiv"); glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programBinarySize); - if (programBinarySize) { - size_t const size = sizeof(Blob) + programBinarySize; - std::unique_ptr blob{ (Blob*)malloc(size), &::free }; - SYSTRACE_NAME("glGetProgramBinary"); - glGetProgramBinary(program, programBinarySize, &programBinarySize, &format, blob->data); + } + if (programBinarySize) { + size_t const size = sizeof(Blob) + programBinarySize; + std::unique_ptr blob{ (Blob*)malloc(size), &::free }; + if (UTILS_LIKELY(blob)) { + { // scope for systrace + SYSTRACE_NAME("glGetProgramBinary"); + glGetProgramBinary(program, programBinarySize, + &programBinarySize, &format, blob->data); + } GLenum const error = glGetError(); if (error == GL_NO_ERROR) { blob->format = format; @@ -108,18 +122,4 @@ void OpenGLBlobCache::insert(Platform& platform, #endif } -void OpenGLBlobCache::insert(Platform& platform, BlobCacheKey const& key, - GLenum format, void* data, GLsizei programBinarySize) noexcept { - SYSTRACE_CALL(); - if (platform.hasBlobFunc()) { - if (programBinarySize) { - size_t const size = sizeof(Blob) + programBinarySize; - std::unique_ptr blob{ (Blob*)malloc(size), &::free }; - blob->format = format; - memcpy(blob->data, data, programBinarySize); - platform.insertBlob(key.data(), key.size(), blob.get(), size); - } - } -} - } // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLBlobCache.h b/filament/backend/src/opengl/OpenGLBlobCache.h index 5569fa20341..fa98f0a4496 100644 --- a/filament/backend/src/opengl/OpenGLBlobCache.h +++ b/filament/backend/src/opengl/OpenGLBlobCache.h @@ -25,20 +25,21 @@ namespace filament::backend { class Platform; class Program; +class OpenGLContext; class OpenGLBlobCache { public: - static GLuint retrieve(BlobCacheKey* key, Platform& platform, - Program const& program) noexcept; + explicit OpenGLBlobCache(OpenGLContext& gl) noexcept; - static void insert(Platform& platform, - BlobCacheKey const& key, GLuint program) noexcept; + GLuint retrieve(BlobCacheKey* key, Platform& platform, + Program const& program) const noexcept; - static void insert(Platform& platform, BlobCacheKey const& key, - GLenum format, void* data, GLsizei programBinarySize) noexcept; + void insert(Platform& platform, + BlobCacheKey const& key, GLuint program) noexcept; private: struct Blob; + bool mCachingSupported = false; }; } // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 072096718b3..ddf629f0f87 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -16,10 +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 @@ -48,7 +63,9 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept { #endif } -OpenGLContext::OpenGLContext() noexcept { +OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept + : mPlatform(platform), + mSamplerMap(32) { state.vao.p = &mDefaultVAO; @@ -99,38 +116,41 @@ OpenGLContext::OpenGLContext() noexcept { if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) { #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 +#ifdef GL_EXT_texture_filter_anisotropic + if (ext.EXT_texture_filter_anisotropic) { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy); + } +#endif + glGetIntegerv(GL_MAX_DRAW_BUFFERS, + &gets.max_draw_buffers); + glGetIntegerv(GL_MAX_SAMPLES, + &gets.max_samples); + glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, + &gets.max_transform_feedback_separate_attribs); glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size); glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &gets.max_uniform_buffer_bindings); + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, + &gets.num_program_binary_formats); glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment); - glGetIntegerv(GL_MAX_SAMPLES, - &gets.max_samples); - glGetIntegerv(GL_MAX_DRAW_BUFFERS, - &gets.max_draw_buffers); - glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, - &gets.max_transform_feedback_separate_attribs); -#ifdef GL_EXT_texture_filter_anisotropic - if (ext.EXT_texture_filter_anisotropic) { - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy); - } -#endif #endif } + #ifdef BACKEND_OPENGL_VERSION_GLES else { + gets.max_anisotropy = 1; + gets.max_draw_buffers = 1; + gets.max_samples = 1; + gets.max_transform_feedback_separate_attribs = 0; gets.max_uniform_block_size = 0; gets.max_uniform_buffer_bindings = 0; + gets.num_program_binary_formats = 0; gets.uniform_buffer_offset_alignment = 0; - gets.max_samples = 1; - gets.max_draw_buffers = 1; - gets.max_transform_feedback_separate_attribs = 0; - gets.max_anisotropy = 1; } #endif - slog.v << "Feature level: " << +mFeatureLevel << '\n'; slog.v << "Active workarounds: " << '\n'; UTILS_NOUNROLL @@ -143,13 +163,29 @@ OpenGLContext::OpenGLContext() noexcept { #ifndef NDEBUG // this is useful for development - slog.v << "GL_MAX_DRAW_BUFFERS = " << gets.max_draw_buffers << '\n' - << "GL_MAX_RENDERBUFFER_SIZE = " << gets.max_renderbuffer_size << '\n' - << "GL_MAX_SAMPLES = " << gets.max_samples << '\n' - << "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = " << gets.max_anisotropy << '\n' - << "GL_MAX_UNIFORM_BLOCK_SIZE = " << gets.max_uniform_block_size << '\n' - << "GL_MAX_TEXTURE_IMAGE_UNITS = " << gets.max_texture_image_units << '\n' - << "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = " << gets.uniform_buffer_offset_alignment << '\n' + slog.v + << "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = " + << gets.max_anisotropy << '\n' + << "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = " + << gets.max_combined_texture_image_units << '\n' + << "GL_MAX_DRAW_BUFFERS = " + << gets.max_draw_buffers << '\n' + << "GL_MAX_RENDERBUFFER_SIZE = " + << gets.max_renderbuffer_size << '\n' + << "GL_MAX_SAMPLES = " + << gets.max_samples << '\n' + << "GL_MAX_TEXTURE_IMAGE_UNITS = " + << gets.max_texture_image_units << '\n' + << "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = " + << gets.max_transform_feedback_separate_attribs << '\n' + << "GL_MAX_UNIFORM_BLOCK_SIZE = " + << gets.max_uniform_block_size << '\n' + << "GL_MAX_UNIFORM_BUFFER_BINDINGS = " + << gets.max_uniform_buffer_bindings << '\n' + << "GL_NUM_PROGRAM_BINARY_FORMATS = " + << gets.num_program_binary_formats << '\n' + << "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = " + << gets.uniform_buffer_offset_alignment << '\n' ; flush(slog.v); #endif @@ -212,6 +248,58 @@ OpenGLContext::OpenGLContext() noexcept { glDebugMessageCallback(cb, nullptr); } #endif + + mTimerQueryFactory = TimerQueryFactory::init(platform, *this); +} + +OpenGLContext::~OpenGLContext() noexcept { +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + if (!isES2()) { + for (auto& item: mSamplerMap) { + unbindSampler(item.second); + glDeleteSamplers(1, &item.second); + } + mSamplerMap.clear(); + } +#endif + delete mTimerQueryFactory; +} + +void OpenGLContext::destroyWithContext( + size_t index, std::function const& closure) noexcept { + if (index == 0) { + // Note: we only need to delay the destruction of objects on the unprotected context + // (index 0) because the protected context is always immediately destroyed and all its + // active objects and bindings are then automatically destroyed. + // TODO: this is only guaranteed for EGLPlatform, but that's the only one we care about. + mDestroyWithNormalContext.push_back(closure); + } +} + +void OpenGLContext::unbindEverything() noexcept { + // TODO: we're supposed to unbind everything here so that resources don't get + // stuck in this context (contextIndex) when destroyed in the other context. + // However, because EGLPlatform always immediately destroys the protected context (1), + // the bindings will automatically be severed when we switch back to the default context. + // Since bindings now only exist in one context, we don't have a ref-counting issue to + // worry about. +} + +void OpenGLContext::synchronizeStateAndCache(size_t index) noexcept { + + // if we're just switching back to context 0, run all the pending destructors + if (index == 0) { + auto list = std::move(mDestroyWithNormalContext); + for (auto&& fn: list) { + fn(*this); + } + } + + // the default FBO could be invalid + mDefaultFbo[index].reset(); + + contextIndex = index; + resetState(); } void OpenGLContext::setDefaultState() noexcept { @@ -255,6 +343,7 @@ void OpenGLContext::setDefaultState() noexcept { glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST); #endif +#if !defined(__EMSCRIPTEN__) if (ext.EXT_clip_control) { #if defined(BACKEND_OPENGL_VERSION_GL) glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); @@ -262,9 +351,11 @@ void OpenGLContext::setDefaultState() noexcept { glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT); #endif } +#endif if (ext.EXT_clip_cull_distance) { glEnable(GL_CLIP_DISTANCE0); + glEnable(GL_CLIP_DISTANCE1); } } @@ -289,7 +380,9 @@ void OpenGLContext::initProcs(Procs* procs, # ifdef BACKEND_OPENGL_VERSION_GL procs->getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL # elif defined(GL_EXT_disjoint_timer_query) - procs->getQueryObjectui64v = glGetQueryObjectui64vEXT; +# ifndef __EMSCRIPTEN__ + procs->getQueryObjectui64v = glGetQueryObjectui64vEXT; +# endif # endif // BACKEND_OPENGL_VERSION_GL // core in ES 3.0 and GL 4.3 @@ -301,6 +394,7 @@ void OpenGLContext::initProcs(Procs* procs, #ifdef BACKEND_OPENGL_VERSION_GLES # ifndef IOS // IOS is guaranteed to have ES3.x +# ifndef __EMSCRIPTEN__ if (UTILS_UNLIKELY(major == 2)) { // Runtime OpenGL version is ES 2.x if (UTILS_LIKELY(ext.OES_vertex_array_object)) { @@ -328,6 +422,7 @@ void OpenGLContext::initProcs(Procs* procs, procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR; } +# endif // __EMSCRIPTEN__ # endif // IOS #else procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB; @@ -401,7 +496,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts, if (strstr(renderer, "Mali-T")) { bugs->disable_glFlush = true; bugs->disable_shared_context_draws = true; - bugs->texture_external_needs_rebind = true; // We have not verified that timer queries work on Mali-T, so we disable to be safe. bugs->dont_use_timer_query = true; } @@ -456,7 +550,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts, // (that should be regardless of ANGLE, but we should double-check) bugs->split_easu = true; } - // TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true` } #ifdef BACKEND_OPENGL_VERSION_GLES @@ -561,17 +654,24 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor // figure out and initialize the extensions we need using namespace std::literals; ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv); +#ifndef __EMSCRIPTEN__ ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv); +#endif ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv); ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv); ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv); +#ifndef __EMSCRIPTEN__ ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv); +#endif ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv); +#ifndef __EMSCRIPTEN__ ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv); ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv); ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv); + ext->EXT_protected_textures = exts.has("GL_EXT_protected_textures"sv); +#endif ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv); -#if !defined(__EMSCRIPTEN__) +#ifndef __EMSCRIPTEN__ ext->EXT_texture_compression_etc2 = true; #endif ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv); @@ -592,6 +692,7 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv); ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv); ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv); + ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv); ext->WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv); ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv); ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv); @@ -656,6 +757,7 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) ext->OES_standard_derivatives = true; ext->OES_texture_npot = true; ext->OES_vertex_array_object = true; + ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv); ext->WEBGL_compressed_texture_etc = false; ext->WEBGL_compressed_texture_s3tc = false; ext->WEBGL_compressed_texture_s3tc_srgb = false; @@ -677,6 +779,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); @@ -787,19 +934,53 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe #endif } -void OpenGLContext::deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept { - procs.deleteVertexArrays(n, arrays); - // if one of the destroyed VAO is bound, clear the binding. - for (GLsizei i = 0; i < n; ++i) { - if (state.vao.p->vao == arrays[i]) { +void OpenGLContext::deleteVertexArray(GLuint vao) noexcept { + if (UTILS_LIKELY(vao)) { + procs.deleteVertexArrays(1, &vao); + // if the destroyed VAO is bound, clear the binding. + if (state.vao.p->vao[contextIndex] == vao) { bindVertexArray(nullptr); - break; } } } +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 +GLuint OpenGLContext::getSamplerSlow(SamplerParams params) const noexcept { + assert_invariant(mSamplerMap.find(params) == mSamplerMap.end()); + + using namespace GLUtils; + + GLuint s; + glGenSamplers(1, &s); + glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin)); + glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag)); + glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS)); + glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT)); + glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR)); + glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode)); + glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc)); + +#if defined(GL_EXT_texture_filter_anisotropic) + if (ext.EXT_texture_filter_anisotropic && + !bugs.texture_filter_anisotropic_broken_on_sampler) { + GLfloat const anisotropy = float(1u << params.anisotropyLog2); + glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, + std::min(gets.max_anisotropy, anisotropy)); + } +#endif + CHECK_GL_ERROR(utils::slog.e) + mSamplerMap[params] = s; + return s; +} +#endif + + void OpenGLContext::resetState() noexcept { // Force GL state to match the Filament state + + // increase the state version so other parts of the state know to reset + state.age++; + if (state.major > 2) { #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.draw_fbo); @@ -816,11 +997,8 @@ void OpenGLContext::resetState() noexcept { glUseProgram(state.program.use); // state.vao - if (state.vao.p) { - procs.bindVertexArray(state.vao.p->vao); - } else { - bindVertexArray(nullptr); - } + state.vao.p = nullptr; + bindVertexArray(nullptr); // state.raster glFrontFace(state.raster.frontFace); @@ -976,7 +1154,22 @@ void OpenGLContext::resetState() noexcept { state.window.viewport.w ); glDepthRangef(state.window.depthRange.x, state.window.depthRange.y); - +} + +void OpenGLContext::createTimerQuery(GLTimerQuery* query) { + mTimerQueryFactory->createTimerQuery(query); +} + +void OpenGLContext::destroyTimerQuery(GLTimerQuery* query) { + mTimerQueryFactory->destroyTimerQuery(query); +} + +void OpenGLContext::beginTimeElapsedQuery(GLTimerQuery* query) { + mTimerQueryFactory->beginTimeElapsedQuery(query); +} + +void OpenGLContext::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) { + mTimerQueryFactory->endTimeElapsedQuery(driver, query); } } // namesapce filament diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index ff8b29cbd54..8f487b4781f 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -17,25 +17,39 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H #define TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H -#include -#include -#include +#include "OpenGLTimerQuery.h" +#include + +#include #include -#include "GLUtils.h" +#include "gl_headers.h" + +#include +#include +#include + +#include +#include + +#include #include -#include +#include +#include #include -#include +#include + +#include +#include namespace filament::backend { class OpenGLPlatform; -class OpenGLContext { +class OpenGLContext final : public TimerQueryFactoryInterface { public: static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = MAX_SAMPLER_COUNT; static constexpr const size_t DUMMY_TEXTURE_BINDING = 7; // highest binding guaranteed to work with ES2 @@ -46,19 +60,29 @@ class OpenGLContext { struct RenderPrimitive { static_assert(MAX_VERTEX_ATTRIBUTE_COUNT <= 16); - GLuint vao = 0; // 4 + GLuint vao[2] = {}; // 4 GLuint elementArray = 0; // 4 - utils::bitset vertexAttribArray; // 2 + mutable utils::bitset vertexAttribArray; // 2 - // If this version number does not match vertexBufferWithObjects->bufferObjectsVersion, - // then the VAO needs to be updated. + // if this differs from vertexBufferWithObjects->bufferObjectsVersion, this VAO needs to + // be updated (see OpenGLDriver::updateVertexArrayObject()) uint8_t vertexBufferVersion = 0; // 1 + + // if this differs from OpenGLContext::state.age, this VAO needs to + // be updated (see OpenGLDriver::updateVertexArrayObject()) + uint8_t stateVersion = 0; // 1 + + // If this differs from OpenGLContext::state.age, this VAO's name needs to be updated. + // See OpenGLContext::bindVertexArray() + uint8_t nameVersion = 0; // 1 + + // Size in bytes of indices in the index buffer uint8_t indicesSize = 0; // 1 // The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced // VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is // immutable. - Handle vertexBufferWithObjects = {}; // 4 + Handle vertexBufferWithObjects; // 4 GLenum getIndicesType() const noexcept { return indicesSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; @@ -67,7 +91,18 @@ class OpenGLContext { static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept; - OpenGLContext() noexcept; + explicit OpenGLContext(OpenGLPlatform& platform) noexcept; + ~OpenGLContext() noexcept final; + + // TimerQueryInterface ------------------------------------------------------------------------ + + // note: OpenGLContext being final ensures (clang) these are not called through the vtable + void createTimerQuery(GLTimerQuery* query) override; + void destroyTimerQuery(GLTimerQuery* query) override; + void beginTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; + + // -------------------------------------------------------------------------------------------- template inline bool isAtLeastGL() const noexcept { @@ -123,10 +158,11 @@ class OpenGLContext { 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(GLuint index) noexcept; - inline void disableVertexAttribArray(GLuint index) noexcept; + inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; + inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; inline void enable(GLenum cap) noexcept; inline void disable(GLenum cap) noexcept; inline void frontFace(GLenum mode) noexcept; @@ -148,19 +184,22 @@ class OpenGLContext { inline void depthRange(GLclampf near, GLclampf far) noexcept; void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept; - void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept; + void deleteVertexArray(GLuint vao) noexcept; + + void destroyWithContext(size_t index, std::function const& closure) noexcept; // glGet*() values struct Gets { GLfloat max_anisotropy; + GLint max_combined_texture_image_units; GLint max_draw_buffers; GLint max_renderbuffer_size; GLint max_samples; - GLint max_uniform_block_size; GLint max_texture_image_units; - GLint max_combined_texture_image_units; GLint max_transform_feedback_separate_attribs; - GLint max_uniform_buffer_bindings; + GLint max_uniform_block_size; + GLint max_uniform_buffer_bindings; + GLint num_program_binary_formats; GLint uniform_buffer_offset_alignment; } gets = {}; @@ -182,6 +221,7 @@ class OpenGLContext { bool EXT_discard_framebuffer; bool EXT_multisampled_render_to_texture2; bool EXT_multisampled_render_to_texture; + bool EXT_protected_textures; bool EXT_shader_framebuffer_fetch; bool EXT_texture_compression_bptc; bool EXT_texture_compression_etc2; @@ -204,6 +244,7 @@ class OpenGLContext { bool OES_standard_derivatives; bool OES_texture_npot; bool OES_vertex_array_object; + bool OVR_multiview2; bool WEBGL_compressed_texture_etc; bool WEBGL_compressed_texture_s3tc; bool WEBGL_compressed_texture_s3tc_srgb; @@ -221,10 +262,6 @@ class OpenGLContext { // Some drivers have gl state issues when drawing from shared contexts bool disable_shared_context_draws; - // Some drivers require the GL_TEXTURE_EXTERNAL_OES target to be bound when - // the texture image changes, even if it's already bound to that texture - bool texture_external_needs_rebind; - // Some web browsers seem to immediately clear the default framebuffer when calling // glInvalidateFramebuffer with WebGL 2.0 bool disable_invalidate_framebuffer; @@ -293,8 +330,19 @@ class OpenGLContext { FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } + // This is the index of the context in use. Must be 0 or 1. This is used to manange the + // OpenGL name of ContainerObjects within each context. + uint32_t contextIndex = 0; + // Try to keep the State structure sorted by data-access patterns struct State { + State() noexcept = default; + // make sure we don't copy this state by accident + State(State const& rhs) = delete; + State(State&& rhs) noexcept = delete; + State& operator=(State const& rhs) = delete; + State& operator=(State&& rhs) noexcept = delete; + GLint major = 0; GLint minor = 0; @@ -399,6 +447,7 @@ class OpenGLContext { vec4gli viewport { 0 }; vec2glf depthRange { 0.0f, 1.0f }; } window; + uint8_t age = 0; } state; struct Procs { @@ -418,9 +467,47 @@ class OpenGLContext { void (* maxShaderCompilerThreadsKHR)(GLuint count); } procs{}; + void unbindEverything() noexcept; + void synchronizeStateAndCache(size_t index) noexcept; + void setEs2UniformBinding(size_t index, GLuint id, void const* data, uint16_t age) noexcept { + mUniformBindings[index] = { id, data, age }; + } + auto getEs2UniformBinding(size_t index) const noexcept { + return mUniformBindings[index]; + } + +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + GLuint getSamplerSlow(SamplerParams sp) const noexcept; + + inline GLuint getSampler(SamplerParams sp) const noexcept { + assert_invariant(!sp.padding0); + assert_invariant(!sp.padding1); + assert_invariant(!sp.padding2); + auto& samplerMap = mSamplerMap; + auto pos = samplerMap.find(sp); + if (UTILS_UNLIKELY(pos == samplerMap.end())) { + return getSamplerSlow(sp); + } + return pos->second; + } +#endif + + 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]; + std::array< + std::tuple, + CONFIG_UNIFORM_BINDING_COUNT> mUniformBindings = {}; + mutable tsl::robin_map mSamplerMap; + + void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept; const std::array, sizeof(bugs)> mBugDatabase{{ { bugs.disable_glFlush, @@ -432,9 +519,6 @@ class OpenGLContext { { bugs.disable_shared_context_draws, "disable_shared_context_draws", ""}, - { bugs.texture_external_needs_rebind, - "texture_external_needs_rebind", - ""}, { bugs.disable_invalidate_framebuffer, "disable_invalidate_framebuffer", ""}, @@ -476,8 +560,6 @@ class OpenGLContext { ""}, }}; - 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; @@ -635,11 +717,26 @@ void OpenGLContext::depthRange(GLclampf near, GLclampf far) noexcept { void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept { RenderPrimitive* vao = p ? const_cast(p) : &mDefaultVAO; update_state(state.vao.p, vao, [&]() { - procs.bindVertexArray(vao->vao); + + // See if we need to create a name for this VAO on the fly, this would happen if: + // - we're not the default VAO, because its name is always 0 + // - our name is 0, this could happen if this VAO was created in the "other" context + // - the nameVersion is out of date *and* we're on the protected context, in this case: + // - the name must be stale from a previous use of this context because we always + // destroy the protected context when we're done with it. + bool const recreateVaoName = p != &mDefaultVAO && + ((vao->vao[contextIndex] == 0) || + (vao->nameVersion != state.age && contextIndex == 1)); + if (UTILS_UNLIKELY(recreateVaoName)) { + vao->nameVersion = state.age; + procs.genVertexArrays(1, &vao->vao[contextIndex]); + } + + procs.bindVertexArray(vao->vao[contextIndex]); // update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER); state.buffers.genericBinding[targetIndex] = vao->elementArray; - if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding)) { + if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding || recreateVaoName)) { // This shouldn't be needed, but it looks like some drivers don't do the implicit // glBindBuffer(). glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->elementArray); @@ -677,40 +774,13 @@ 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); update_state(state.textures.units[unit].targets[targetIndex].texture_id, texId, [&]() { activeTexture(unit); glBindTexture(target, texId); - }, (target == GL_TEXTURE_EXTERNAL_OES) && bugs.texture_external_needs_rebind); + }, target == GL_TEXTURE_EXTERNAL_OES); } void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept { @@ -723,20 +793,22 @@ void OpenGLContext::useProgram(GLuint program) noexcept { }); } -void OpenGLContext::enableVertexAttribArray(GLuint index) noexcept { - assert_invariant(state.vao.p); - assert_invariant(index < state.vao.p->vertexAttribArray.size()); - if (UTILS_UNLIKELY(!state.vao.p->vertexAttribArray[index])) { - state.vao.p->vertexAttribArray.set(index); +void OpenGLContext::enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { + assert_invariant(rp); + assert_invariant(index < rp->vertexAttribArray.size()); + bool const force = rp->stateVersion != state.age; + if (UTILS_UNLIKELY(force || !rp->vertexAttribArray[index])) { + rp->vertexAttribArray.set(index); glEnableVertexAttribArray(index); } } -void OpenGLContext::disableVertexAttribArray(GLuint index) noexcept { - assert_invariant(state.vao.p); - assert_invariant(index < state.vao.p->vertexAttribArray.size()); - if (UTILS_UNLIKELY(state.vao.p->vertexAttribArray[index])) { - state.vao.p->vertexAttribArray.unset(index); +void OpenGLContext::disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { + assert_invariant(rp); + assert_invariant(index < rp->vertexAttribArray.size()); + bool const force = rp->stateVersion != state.age; + if (UTILS_UNLIKELY(force || rp->vertexAttribArray[index])) { + rp->vertexAttribArray.unset(index); glDisableVertexAttribArray(index); } } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 2f0e6f618a7..51384e28ab0 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 @@ -90,26 +123,28 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform, #if 0 // this is useful for development, but too verbose even for debug builds // For reference on a 64-bits machine in Release mode: - // GLFence : 8 few // GLIndexBuffer : 8 moderate - // GLSamplerGroup : 8 few - // -- less than or equal 16 bytes - // GLBufferObject : 24 many - // GLSync : 24 few - // GLTimerQuery : 32 few - // OpenGLProgram : 32 moderate - // GLRenderPrimitive : 48 many + // GLSamplerGroup : 16 few + // GLSwapChain : 16 few + // GLTimerQuery : 16 few + // GLFence : 24 few + // GLRenderPrimitive : 32 many + // GLBufferObject : 32 many + // -- less than or equal 32 bytes + // OpenGLProgram : 56 moderate + // GLTexture : 64 moderate // -- less than or equal 64 bytes - // GLTexture : 72 moderate + // GLVertexBuffer : 76 moderate + // GLStream : 104 few // GLRenderTarget : 112 few - // GLStream : 184 few - // GLVertexBuffer : 200 moderate - // -- less than or equal to 208 bytes + // GLVertexBufferInfo : 132 moderate + // -- less than or equal to 136 bytes slog.d - << "HwFence: " << sizeof(HwFence) + << "\nGLSwapChain: " << sizeof(GLSwapChain) << "\nGLBufferObject: " << sizeof(GLBufferObject) << "\nGLVertexBuffer: " << sizeof(GLVertexBuffer) + << "\nGLVertexBufferInfo: " << sizeof(GLVertexBufferInfo) << "\nGLIndexBuffer: " << sizeof(GLIndexBuffer) << "\nGLSamplerGroup: " << sizeof(GLSamplerGroup) << "\nGLRenderPrimitive: " << sizeof(GLRenderPrimitive) @@ -117,7 +152,7 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform, << "\nGLTimerQuery: " << sizeof(GLTimerQuery) << "\nGLStream: " << sizeof(GLStream) << "\nGLRenderTarget: " << sizeof(GLRenderTarget) - << "\nGLSync: " << sizeof(GLSync) + << "\nGLFence: " << sizeof(GLFence) << "\nOpenGLProgram: " << sizeof(OpenGLProgram) << io::endl; #endif @@ -147,9 +182,9 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform, #endif size_t const defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U; - Platform::DriverConfig const validConfig { - .handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize) }; - OpenGLDriver* const driver = new OpenGLDriver(ec, validConfig); + Platform::DriverConfig validConfig{ driverConfig }; + validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize); + OpenGLDriver* const driver = new(std::nothrow) OpenGLDriver(ec, validConfig); return driver; } @@ -168,11 +203,13 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept { OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept : mPlatform(*platform), - mContext(), + mContext(mPlatform), mShaderCompilerService(*this), - mHandleAllocator("Handles", driverConfig.handleArenaSize), - mSamplerMap(32) { - + mHandleAllocator("Handles", + driverConfig.handleArenaSize, + driverConfig.disableHandleUseAfterFreeCheck), + mDriverConfig(driverConfig) { + std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr); // set a reasonable default value for our stream array @@ -191,8 +228,6 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi assert_invariant(mContext.ext.EXT_disjoint_timer_query); #endif - mTimerQueryImpl = OpenGLTimerQueryFactory::init(mPlatform, *this); - mShaderCompilerService.init(); } @@ -227,18 +262,8 @@ void OpenGLDriver::terminate() { // because we called glFinish(), all callbacks should have been executed assert_invariant(mGpuCommandCompleteOps.empty()); - - if (!getContext().isES2()) { - for (auto& item: mSamplerMap) { - mContext.unbindSampler(item.second); - glDeleteSamplers(1, &item.second); - } - mSamplerMap.clear(); - } #endif - delete mTimerQueryImpl; - mPlatform.terminate(); } @@ -262,21 +287,27 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept { mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.targetIndex); } -void OpenGLDriver::useProgram(OpenGLProgram* p) noexcept { +bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept { + if (UTILS_UNLIKELY(!p->isValid())) { + // If the program is not valid, we can't call use(). + return false; + } + // set-up textures and samplers in the proper TMUs (as specified in setSamplers) p->use(this, mContext); if (UTILS_UNLIKELY(mContext.isES2())) { for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) { - auto [buffer, age] = mUniformBindings[i]; + auto [id, buffer, age] = mContext.getEs2UniformBinding(i); if (buffer) { - p->updateUniforms(i, buffer, age); + p->updateUniforms(i, id, buffer, age); } } // Set the output colorspace for this program (linear or rec709). This in only relevant // when mPlatform.isSRGBSwapChainSupported() is false (no need to check though). p->setRec709ColorSpace(mRec709OutputColorspace); } + return true; } @@ -379,6 +410,10 @@ void OpenGLDriver::setStencilState(StencilState ss) noexcept { // Creating driver objects // ------------------------------------------------------------------------------------------------ +Handle OpenGLDriver::createVertexBufferInfoS() noexcept { + return initHandle(); +} + Handle OpenGLDriver::createVertexBufferS() noexcept { return initHandle(); } @@ -439,14 +474,21 @@ Handle OpenGLDriver::createTimerQueryS() noexcept { return initHandle(); } -void OpenGLDriver::createVertexBufferR( - Handle vbh, +void OpenGLDriver::createVertexBufferInfoR( + Handle vbih, uint8_t bufferCount, uint8_t attributeCount, - uint32_t elementCount, AttributeArray attributes) { DEBUG_MARKER() - construct(vbh, bufferCount, attributeCount, elementCount, attributes); + construct(vbih, bufferCount, attributeCount, attributes); +} + +void OpenGLDriver::createVertexBufferR( + Handle vbh, + uint32_t vertexCount, + Handle vbih) { + DEBUG_MARKER() + construct(vbh, vertexCount, vbih); } void OpenGLDriver::createIndexBufferR( @@ -479,6 +521,7 @@ void OpenGLDriver::createBufferObjectR(Handle boh, GLBufferObject* bo = construct(boh, byteCount, bindingType, usage); if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { + bo->gl.id = ++mLastAssignedEmulatedUboId; bo->gl.buffer = malloc(byteCount); memset(bo->gl.buffer, 0, byteCount); } else { @@ -493,31 +536,33 @@ void OpenGLDriver::createBufferObjectR(Handle boh, void OpenGLDriver::createRenderPrimitiveR(Handle rph, Handle vbh, Handle ibh, - PrimitiveType pt, uint32_t offset, - uint32_t minIndex, uint32_t maxIndex, uint32_t count) { + PrimitiveType pt) { DEBUG_MARKER() auto& gl = mContext; - GLVertexBuffer const* const eb = handle_cast(vbh); GLIndexBuffer const* const ib = handle_cast(ibh); assert_invariant(ib->elementSize == 2 || ib->elementSize == 4); - GLRenderPrimitive* rp = handle_cast(rph); + GLVertexBuffer* const vb = handle_cast(vbh); + GLRenderPrimitive* const rp = handle_cast(rph); rp->gl.indicesSize = (ib->elementSize == 4u) ? 4u : 2u; rp->gl.vertexBufferWithObjects = vbh; rp->type = pt; - rp->offset = offset * rp->gl.indicesSize; - rp->count = count; - rp->minIndex = minIndex; - rp->maxIndex = maxIndex > minIndex ? maxIndex : rp->maxVertexCount - 1; // sanitize max index + rp->vbih = vb->vbih; + + // create a name for this VAO in the current context + gl.procs.genVertexArrays(1, &rp->gl.vao[gl.contextIndex]); - gl.procs.genVertexArrays(1, &rp->gl.vao); + // this implies our name is up-to-date + rp->gl.nameVersion = gl.state.age; + // binding the VAO will actually create it gl.bindVertexArray(&rp->gl); - // update the VBO bindings in the VAO - updateVertexArrayObject(rp, eb); + // Note: we don't update the vertex buffer bindings in the VAO just yet, we will do that + // later in draw() or bindRenderPrimitive(). At this point, the HwVertexBuffer might not + // have all its buffers set. // this records the index buffer into the currently bound VAO gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer); @@ -530,7 +575,7 @@ void OpenGLDriver::createProgramR(Handle ph, Program&& program) { if (UTILS_UNLIKELY(mContext.isES2())) { - // Here we patch the specification constants to enable or not the rec709 output + // Here we patch the specialization constants to enable or not the rec709 output // color space emulation in this program. Obviously, the backend shouldn't know about // specific spec-constants, so we need to handle failures gracefully. This cannot be // done at Material creation time because only the backend has access to @@ -555,7 +600,8 @@ void OpenGLDriver::createProgramR(Handle ph, Program&& program) { CHECK_GL_ERROR(utils::slog.e) } -void OpenGLDriver::createSamplerGroupR(Handle sbh, uint32_t size) { +void OpenGLDriver::createSamplerGroupR(Handle sbh, uint32_t size, + utils::FixedSizeString<32> debugName) { DEBUG_MARKER() construct(sbh, size); @@ -563,13 +609,22 @@ void OpenGLDriver::createSamplerGroupR(Handle sbh, uint32_t size UTILS_NOINLINE void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t, - uint32_t width, uint32_t height, uint32_t depth) noexcept { + uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept { auto& gl = mContext; bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); +#ifdef GL_EXT_protected_textures +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + if (UTILS_UNLIKELY(useProtectedMemory)) { + assert_invariant(gl.ext.EXT_protected_textures); + glTexParameteri(t->gl.target, GL_TEXTURE_PROTECTED_EXT, 1); + } +#endif +#endif + switch (t->gl.target) { case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: @@ -594,8 +649,9 @@ void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t, } } else { glTexImage2D(t->gl.target, level, GLint(t->gl.internalFormat), - GLsizei(width), GLsizei(height), 0, - format, type, nullptr); + std::max(GLsizei(1), GLsizei(width >> level)), + std::max(GLsizei(1), GLsizei(height >> level)), + 0, format, type, nullptr); } } } @@ -659,6 +715,21 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint GLenum internalFormat = getInternalFormat(format); assert_invariant(internalFormat); + if (any(usage & TextureUsage::PROTECTED)) { + // renderbuffers don't have a protected mode, so we need to use a texture + // because protected textures are only supported on GLES 3.2, MSAA will be available. + usage |= TextureUsage::SAMPLEABLE; + } else if (any(usage & TextureUsage::UPLOADABLE)) { + // if we have the uploadable flag, we also need to use a texture + usage |= TextureUsage::SAMPLEABLE; + } else if (target != SamplerType::SAMPLER_2D) { + // renderbuffers can only be 2D + usage |= TextureUsage::SAMPLEABLE; + } else if (levels > 1) { + // renderbuffers can't have mip levels + usage |= TextureUsage::SAMPLEABLE; + } + auto& gl = mContext; samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples)); GLTexture* t = construct(th, target, levels, samples, w, h, depth, format, usage); @@ -694,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; } @@ -722,21 +793,16 @@ 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. } #endif } - textureStorage(t, w, h, depth); + + textureStorage(t, w, h, depth, bool(usage & TextureUsage::PROTECTED)); } } else { - assert_invariant(any(usage & ( - TextureUsage::COLOR_ATTACHMENT | - TextureUsage::DEPTH_ATTACHMENT | - TextureUsage::STENCIL_ATTACHMENT))); - assert_invariant(levels == 1); - assert_invariant(target == SamplerType::SAMPLER_2D); t->gl.internalFormat = internalFormat; t->gl.target = GL_RENDERBUFFER; glGenRenderbuffers(1, &t->gl.id); @@ -791,27 +857,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; } @@ -822,7 +888,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. } @@ -833,61 +899,65 @@ void OpenGLDriver::importTextureR(Handle th, intptr_t id, } void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) { - - auto& gl = mContext; - // NOTE: this is called from draw() and must be as efficient as possible. + auto& gl = mContext; if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) { // The VAO for the given render primitive must already be bound. -#ifndef NDEBUG GLint vaoBinding; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding); - assert_invariant(vaoBinding == (GLint)rp->gl.vao); -#endif - rp->gl.vertexBufferVersion = vb->bufferObjectsVersion; - } else { - // if we don't have OES_vertex_array_object, we never update the buffer version so - // that it's always reset in draw + assert_invariant(vaoBinding == (GLint)rp->gl.vao[gl.contextIndex]); } - rp->maxVertexCount = vb->vertexCount; - for (size_t i = 0, n = vb->attributes.size(); i < n; i++) { - const auto& attribute = vb->attributes[i]; - const uint8_t bi = attribute.buffer; + if (UTILS_LIKELY(rp->gl.vertexBufferVersion == vb->bufferObjectsVersion && + rp->gl.stateVersion == gl.state.age)) { + return; + } + + GLVertexBufferInfo const* const vbi = handle_cast(vb->vbih); - // Invoking glVertexAttribPointer without a bound VBO is an invalid operation, so we must - // take care to avoid it. This can occur when VertexBuffer is only partially populated with - // BufferObject items. - if (bi != Attribute::BUFFER_UNUSED && UTILS_LIKELY(vb->gl.buffers[bi] != 0)) { + for (size_t i = 0, n = vbi->attributes.size(); i < n; i++) { + const auto& attribute = vbi->attributes[i]; + const uint8_t bi = attribute.buffer; + if (bi != Attribute::BUFFER_UNUSED) { + // if a buffer is defined it must not be invalid. + assert_invariant(vb->gl.buffers[bi]); + // if w're on ES2, the user shouldn't use FLAG_INTEGER_TARGET assert_invariant(!(gl.isES2() && (attribute.flags & Attribute::FLAG_INTEGER_TARGET))); gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]); + GLuint const index = i; + 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; + void const* pointer = reinterpret_cast(attribute.offset); + #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) { - glVertexAttribIPointer(GLuint(i), - (GLint)getComponentCount(attribute.type), - getComponentType(attribute.type), - attribute.stride, - (void*) uintptr_t(attribute.offset)); + // integer attributes can't be floats + assert_invariant(type == GL_BYTE || type == GL_UNSIGNED_BYTE || type == GL_SHORT || + type == GL_UNSIGNED_SHORT || type == GL_INT || type == GL_UNSIGNED_INT); + glVertexAttribIPointer(index, size, type, stride, pointer); } else #endif { - glVertexAttribPointer(GLuint(i), - (GLint)getComponentCount(attribute.type), - getComponentType(attribute.type), - getNormalization(attribute.flags & Attribute::FLAG_NORMALIZED), - attribute.stride, - (void*) uintptr_t(attribute.offset)); + glVertexAttribPointer(index, size, type, normalized, stride, pointer); } - gl.enableVertexAttribArray(GLuint(i)); + gl.enableVertexAttribArray(&rp->gl, GLuint(i)); } else { - // In some OpenGL implementations, we must supply a properly-typed placeholder for - // every integer input that is declared in the vertex shader, even if disabled. + // every integer input that is declared in the vertex shader. + // Note that the corresponding doesn't have to be enabled and in fact won't be. If it + // was enabled, it would indicate a user-error (providing the wrong type of array). + // With a disabled array, the vertex shader gets the attribute from glVertexAttrib, + // and must have the proper intergerness. + // But at this point, we don't know what the shader requirements are, and so we must + // rely on the attribute. + #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) { if (!gl.isES2()) { @@ -900,13 +970,21 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer glVertexAttrib4f(GLuint(i), 0, 0, 0, 0); } - gl.disableVertexAttribArray(GLuint(i)); + gl.disableVertexAttribArray(&rp->gl, GLuint(i)); } } + + rp->gl.stateVersion = gl.state.age; + if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) { + rp->gl.vertexBufferVersion = vb->bufferObjectsVersion; + } else { + // if we don't have OES_vertex_array_object, we never update the buffer version so + // that it's always reset in draw + } } void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, - 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 @@ -1044,9 +1122,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: @@ -1055,6 +1144,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, } CHECK_GL_ERROR(utils::slog.e) } else +#ifndef __EMSCRIPTEN__ #ifdef GL_EXT_multisampled_render_to_texture // EXT_multisampled_render_to_texture only support GL_COLOR_ATTACHMENT0 if (!attachmentTypeNotSupportedByMSRTT && (t->depth <= 1) @@ -1067,7 +1157,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, // This extension only exists on OpenGL ES. gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo); if (any(t->usage & TextureUsage::SAMPLEABLE)) { - glext::glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, + glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, attachment, target, t->gl.id, binfo.level, rt->gl.samples); } else { glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, @@ -1075,7 +1165,8 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, } CHECK_GL_ERROR(utils::slog.e) } else -#endif +#endif // GL_EXT_multisampled_render_to_texture +#endif // __EMSCRIPTEN__ if (!any(t->usage & TextureUsage::SAMPLEABLE) && t->samples > 1) { assert_invariant(rt->gl.samples > 1); assert_invariant(glIsRenderbuffer(t->gl.id)); @@ -1174,6 +1265,7 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32 uint32_t height, uint8_t samples) const noexcept { glBindRenderbuffer(GL_RENDERBUFFER, rbo); if (samples > 1) { +#ifndef __EMSCRIPTEN__ #ifdef GL_EXT_multisampled_render_to_texture auto& gl = mContext; if (gl.ext.EXT_multisampled_render_to_texture || @@ -1181,7 +1273,8 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32 glext::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, internalformat, width, height); } else -#endif +#endif // GL_EXT_multisampled_render_to_texture +#endif // __EMSCRIPTEN__ { #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 glRenderbufferStorageMultisample(GL_RENDERBUFFER, @@ -1203,11 +1296,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; @@ -1218,6 +1309,7 @@ void OpenGLDriver::createRenderTargetR(Handle rth, uint32_t width, uint32_t height, uint8_t samples, + uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { @@ -1284,12 +1376,14 @@ 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); } } - glDrawBuffers((GLsizei)maxDrawBuffers, bufs); + if (UTILS_LIKELY(!getContext().isES2())) { + glDrawBuffers((GLsizei)maxDrawBuffers, bufs); + } CHECK_GL_ERROR(utils::slog.e) } #endif @@ -1304,7 +1398,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); } @@ -1315,13 +1409,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); } } @@ -1363,6 +1457,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 && @@ -1377,6 +1478,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 && @@ -1387,13 +1495,21 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle sch, void OpenGLDriver::createTimerQueryR(Handle tqh, int) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->createTimerQuery(tq); + mContext.createTimerQuery(tq); } // ------------------------------------------------------------------------------------------------ // Destroying driver objects // ------------------------------------------------------------------------------------------------ +void OpenGLDriver::destroyVertexBufferInfo(Handle vbih) { + DEBUG_MARKER() + if (vbih) { + GLVertexBufferInfo const* vbi = handle_cast(vbih); + destruct(vbih, vbi); + } +} + void OpenGLDriver::destroyVertexBuffer(Handle vbh) { DEBUG_MARKER() if (vbh) { @@ -1433,7 +1549,20 @@ void OpenGLDriver::destroyRenderPrimitive(Handle rph) { if (rph) { auto& gl = mContext; GLRenderPrimitive const* rp = handle_cast(rph); - gl.deleteVertexArrays(1, &rp->gl.vao); + gl.deleteVertexArray(rp->gl.vao[gl.contextIndex]); + + // If we have a name in the "other" context, we need to schedule the destroy for + // later, because it can't be done here. VAOs are "container objects" and are not + // shared between contexts. + size_t const otherContextIndex = 1 - gl.contextIndex; + GLuint const nameInOtherContext = rp->gl.vao[otherContextIndex]; + if (UTILS_UNLIKELY(nameInOtherContext)) { + gl.destroyWithContext(otherContextIndex, + [name = nameInOtherContext](OpenGLContext& gl) { + gl.deleteVertexArray(name); + }); + } + destruct(rph, rp); } } @@ -1450,6 +1579,11 @@ void OpenGLDriver::destroySamplerGroup(Handle sbh) { DEBUG_MARKER() if (sbh) { GLSamplerGroup* sb = handle_cast(sbh); + for (auto& binding : mSamplerBindings) { + if (binding == sb) { + binding = nullptr; + } + } destruct(sbh, sb); } } @@ -1458,9 +1592,9 @@ void OpenGLDriver::destroyTexture(Handle th) { DEBUG_MARKER() if (th) { + auto& gl = mContext; GLTexture* t = handle_cast(th); if (UTILS_LIKELY(!t->gl.imported)) { - auto& gl = mContext; if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) { gl.unbindTexture(t->gl.target, t->gl.id); if (UTILS_UNLIKELY(t->hwStream)) { @@ -1478,6 +1612,8 @@ void OpenGLDriver::destroyTexture(Handle th) { if (t->gl.sidecarRenderBufferMS) { glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS); } + } else { + gl.unbindTexture(t->gl.target, t->gl.id); } destruct(th, t); } @@ -1491,11 +1627,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 @@ -1567,7 +1703,7 @@ void OpenGLDriver::destroyTimerQuery(Handle tqh) { if (tqh) { GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->destroyTimerQuery(tq); + mContext.destroyTimerQuery(tq); destruct(tqh, tq); } } @@ -1774,6 +1910,10 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat format) { // support more formats, but it requires querying GL_INTERNALFORMAT_SUPPORTED which is not // available in OpenGL ES. auto& gl = mContext; + if (UTILS_UNLIKELY(gl.isES2())) { + auto [es2format, type] = textureFormatToFormatAndType(format); + return es2format != GL_NONE && type != GL_NONE; + } switch (format) { // Core formats. case TextureFormat::R8: @@ -1854,7 +1994,7 @@ bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() { } bool OpenGLDriver::isFrameTimeSupported() { - return OpenGLTimerQueryFactory::isGpuTimeSupported(); + return TimerQueryFactory::isGpuTimeSupported(); } bool OpenGLDriver::isAutoDepthResolveSupported() { @@ -1872,18 +2012,38 @@ bool OpenGLDriver::isSRGBSwapChainSupported() { return mPlatform.isSRGBSwapChainSupported(); } -bool OpenGLDriver::isStereoSupported() { - // Stereo requires instancing and EXT_clip_cull_distance. +bool OpenGLDriver::isProtectedContentSupported() { + return mPlatform.isProtectedContextSupported(); +} + +bool OpenGLDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) { + // Instanced-stereo requires instancing and EXT_clip_cull_distance. + // Multiview-stereo requires ES 3.0 and OVR_multiview2. if (UTILS_UNLIKELY(mContext.isES2())) { return false; } - return mContext.ext.EXT_clip_cull_distance; + switch (stereoscopicType) { + case backend::StereoscopicType::INSTANCED: + return mContext.ext.EXT_clip_cull_distance; + case backend::StereoscopicType::MULTIVIEW: + return mContext.ext.OVR_multiview2; + default: + return false; + } } bool OpenGLDriver::isParallelShaderCompileSupported() { return mShaderCompilerService.isParallelShaderCompileSupported(); } +bool OpenGLDriver::isDepthStencilResolveSupported() { + return true; +} + +bool OpenGLDriver::isProtectedTexturesSupported() { + return getContext().ext.EXT_protected_textures; +} + bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) { switch (workaround) { case Workaround::SPLIT_EASU: @@ -1951,8 +2111,26 @@ void OpenGLDriver::makeCurrent(Handle schDraw, Handle GLSwapChain* scDraw = handle_cast(schDraw); GLSwapChain* scRead = handle_cast(schRead); - mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain); + + mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain, + [this]() { + // OpenGL context is about to change, unbind everything + mContext.unbindEverything(); + }, + [this](size_t index) { + // OpenGL context has changed, resynchronize the state with the cache + mContext.synchronizeStateAndCache(index); + slog.d << "*** OpenGL context change : " << (index ? "protected" : "default") << io::endl; + }); + mCurrentDrawSwapChain = scDraw; + + // From the GL spec for glViewport and glScissor: + // When a GL context is first attached to a window, width and height are set to the + // dimensions of that window. + // So basically, our viewport/scissor can be reset to "something" here. + mContext.state.window.viewport = {}; + mContext.state.window.scissor = {}; } // ------------------------------------------------------------------------------------------------ @@ -2158,7 +2336,7 @@ void OpenGLDriver::updateSamplerGroup(Handle sbh, #endif #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 if (UTILS_LIKELY(!es2)) { - samplerId = getSampler(params); + samplerId = mContext.getSampler(params); } else #endif { @@ -2198,8 +2376,7 @@ void OpenGLDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint t->gl.baseLevel = (int8_t)minLevel; glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - t->gl - .maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000 + t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000 glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); } #endif @@ -2249,16 +2426,13 @@ void OpenGLDriver::generateMipmaps(Handle th) { CHECK_GL_ERROR(utils::slog.e) } -bool OpenGLDriver::canGenerateMipmaps() { - return true; -} - void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, PixelBufferDescriptor&& p) { auto& gl = mContext; + assert_invariant(t != nullptr); assert_invariant(xoffset + width <= std::max(1u, t->width >> level)); assert_invariant(yoffset + height <= std::max(1u, t->height >> level)); assert_invariant(t->samples <= 1); @@ -2268,8 +2442,16 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level, return; } - GLenum const glFormat = getFormat(p.format); - GLenum const glType = getType(p.type); + GLenum glFormat; + GLenum glType; + if (mContext.isES2()) { + auto formatAndType = textureFormatToFormatAndType(t->format); + glFormat = formatAndType.first; + glType = formatAndType.second; + } else { + glFormat = getFormat(p.format); + glType = getType(p.type); + } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 if (!gl.isES2()) { @@ -2283,7 +2465,9 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level, size_t const stride = p.stride ? p.stride : width; size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1); size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment); - void const* const buffer = static_cast(p.buffer) + p.left * bpp + bpr * p.top; + size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride" + void const* const buffer = static_cast(p.buffer) + + bpp* p.left + bpr * p.top + bpl * 0; // TODO: PBD should have a p.depth switch (t->target) { case SamplerType::SAMPLER_EXTERNAL: @@ -2348,7 +2532,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); @@ -2449,7 +2633,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); @@ -2588,21 +2772,21 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce void OpenGLDriver::beginTimerQuery(Handle tqh) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->beginTimeElapsedQuery(tq); + mContext.beginTimeElapsedQuery(tq); } void OpenGLDriver::endTimerQuery(Handle tqh) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->endTimeElapsedQuery(tq); + mContext.endTimeElapsedQuery(*this, tq); } -bool OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { +TimerQueryResult OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { GLTimerQuery* tq = handle_cast(tqh); - return OpenGLTimerQueryInterface::getTimerQueryValue(tq, elapsedTime); + 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); @@ -2630,13 +2814,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()); } @@ -2724,9 +2908,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()); } @@ -2788,50 +2972,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; } @@ -2879,7 +3061,10 @@ void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind assert_invariant(offset + size <= ub->byteCount); if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { - mUniformBindings[index] = { static_cast(ub->gl.buffer) + offset, ub->age }; + gl.setEs2UniformBinding(index, + ub->gl.id, + static_cast(ub->gl.buffer) + offset, + ub->age); } else { GLenum const target = GLUtils::getBufferBindingType(bindingType); @@ -2897,7 +3082,7 @@ void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) auto& gl = mContext; if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { - mUniformBindings[index] = {}; + gl.setEs2UniformBinding(index, 0, nullptr, 0); return; } @@ -2914,47 +3099,19 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle sbh) { CHECK_GL_ERROR(utils::slog.e) } - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 -GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept { - assert_invariant(mSamplerMap.find(params) == mSamplerMap.end()); - - GLuint s; - glGenSamplers(1, &s); - glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin)); - glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag)); - glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS)); - glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT)); - glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR)); - glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode)); - glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc)); - -#if defined(GL_EXT_texture_filter_anisotropic) - auto& gl = mContext; - if (gl.ext.EXT_texture_filter_anisotropic && - !gl.bugs.texture_filter_anisotropic_broken_on_sampler) { - GLfloat const anisotropy = float(1u << params.anisotropyLog2); - glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, - std::min(gl.gets.max_anisotropy, anisotropy)); - } -#endif - CHECK_GL_ERROR(utils::slog.e) - mSamplerMap[params] = s; - return s; -} -#endif - void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) { +#ifndef __EMSCRIPTEN__ #ifdef GL_EXT_debug_marker auto& gl = mContext; if (gl.ext.EXT_debug_marker) { glInsertEventMarkerEXT(GLsizei(len ? len : strlen(string)), string); } #endif +#endif } void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) { - +#ifndef __EMSCRIPTEN__ #ifdef GL_EXT_debug_marker #if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) { @@ -2967,9 +3124,11 @@ void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) { SYSTRACE_CONTEXT(); SYSTRACE_NAME_BEGIN(string); #endif +#endif } void OpenGLDriver::popGroupMarker(int) { +#ifndef __EMSCRIPTEN__ #ifdef GL_EXT_debug_marker #if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) { @@ -2982,6 +3141,7 @@ void OpenGLDriver::popGroupMarker(int) { SYSTRACE_CONTEXT(); SYSTRACE_NAME_END(); #endif +#endif } void OpenGLDriver::startCapture(int) { @@ -3397,75 +3557,230 @@ void OpenGLDriver::clearWithRasterPipe(TargetBufferFlags clearFlags, CHECK_GL_ERROR(utils::slog.e) } -void OpenGLDriver::blit(TargetBufferFlags buffers, +void OpenGLDriver::resolve( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, + Handle src, uint8_t dstLevel, uint8_t dstLayer) { + DEBUG_MARKER() + GLTexture const* const s = handle_cast(src); + GLTexture const* const d = handle_cast(dst); + assert_invariant(s); + assert_invariant(d); + + ASSERT_PRECONDITION( + d->width == s->width && d->height == s->height, + "invalid resolve: src and dst sizes don't match"); + + ASSERT_PRECONDITION(s->samples > 1 && d->samples == 1, + "invalid resolve: src.samples=%u, dst.samples=%u", + +s->samples, +d->samples); + + blit( dst, dstLevel, dstLayer, {}, + src, srcLevel, srcLayer, {}, + { d->width, d->height }); +} + +void OpenGLDriver::blit( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, uint2 dstOrigin, + Handle src, uint8_t dstLevel, uint8_t dstLayer, uint2 srcOrigin, + uint2 size) { + DEBUG_MARKER() + UTILS_UNUSED_IN_RELEASE auto& gl = mContext; + assert_invariant(!gl.isES2()); + +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + + GLTexture* d = handle_cast(dst); + GLTexture* s = handle_cast(src); + assert_invariant(d); + assert_invariant(s); + + ASSERT_PRECONDITION_NON_FATAL(any(d->usage & TextureUsage::BLIT_DST), + "texture doesn't have BLIT_DST"); + + ASSERT_PRECONDITION_NON_FATAL(any(s->usage & TextureUsage::BLIT_SRC), + "texture doesn't have BLIT_SRC"); + + ASSERT_PRECONDITION_NON_FATAL(s->format == d->format, + "src and dst texture format don't match"); + + enum class AttachmentType : GLenum { + COLOR = GL_COLOR_ATTACHMENT0, + DEPTH = GL_DEPTH_ATTACHMENT, + STENCIL = GL_STENCIL_ATTACHMENT, + DEPTH_STENCIL = GL_DEPTH_STENCIL_ATTACHMENT, + }; + + auto getFormatType = [](TextureFormat format) -> AttachmentType { + bool const depth = isDepthFormat(format); + bool const stencil = isStencilFormat(format); + if (depth && stencil) return AttachmentType::DEPTH_STENCIL; + if (depth) return AttachmentType::DEPTH; + if (stencil) return AttachmentType::STENCIL; + return AttachmentType::COLOR; + }; + + AttachmentType const type = getFormatType(d->format); + assert_invariant(type == getFormatType(s->format)); + + // GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or + // GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST. + GLbitfield mask = {}; + switch (type) { + case AttachmentType::COLOR: + mask = GL_COLOR_BUFFER_BIT; + break; + case AttachmentType::DEPTH: + mask = GL_DEPTH_BUFFER_BIT; + break; + case AttachmentType::STENCIL: + mask = GL_STENCIL_BUFFER_BIT; + break; + case AttachmentType::DEPTH_STENCIL: + mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + break; + }; + + GLuint fbo[2] = {}; + glGenFramebuffers(2, fbo); + + gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[0]); + switch (d->target) { + case SamplerType::SAMPLER_2D: + if (any(d->usage & TextureUsage::SAMPLEABLE)) { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type), + GL_TEXTURE_2D, d->gl.id, dstLevel); + } else { + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GLenum(type), + GL_RENDERBUFFER, d->gl.id); + } + break; + case SamplerType::SAMPLER_CUBEMAP: + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type), + GL_TEXTURE_CUBE_MAP_POSITIVE_X + dstLayer, d->gl.id, dstLevel); + break; + case SamplerType::SAMPLER_2D_ARRAY: + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + case SamplerType::SAMPLER_3D: + glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GLenum(type), + d->gl.id, dstLevel, dstLayer); + break; + case SamplerType::SAMPLER_EXTERNAL: + break; + } + CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER) + + gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]); + switch (s->target) { + case SamplerType::SAMPLER_2D: + if (any(s->usage & TextureUsage::SAMPLEABLE)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type), + GL_TEXTURE_2D, s->gl.id, srcLevel); + } else { + glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GLenum(type), + GL_RENDERBUFFER, s->gl.id); + } + break; + case SamplerType::SAMPLER_CUBEMAP: + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type), + GL_TEXTURE_CUBE_MAP_POSITIVE_X + srcLayer, s->gl.id, srcLevel); + break; + case SamplerType::SAMPLER_2D_ARRAY: + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + case SamplerType::SAMPLER_3D: + glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GLenum(type), + s->gl.id, srcLevel, srcLayer); + break; + case SamplerType::SAMPLER_EXTERNAL: + break; + } + CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER) + + gl.disable(GL_SCISSOR_TEST); + glBlitFramebuffer( + srcOrigin.x, srcOrigin.y, srcOrigin.x + size.x, srcOrigin.y + size.y, + dstOrigin.x, dstOrigin.y, dstOrigin.x + size.x, dstOrigin.y + size.y, + mask, GL_NEAREST); + CHECK_GL_ERROR(utils::slog.e) + + gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER); + gl.unbindFramebuffer(GL_READ_FRAMEBUFFER); + glDeleteFramebuffers(2, fbo); + + if (any(d->usage & TextureUsage::SAMPLEABLE)) { + // In a sense, blitting to a texture level is similar to calling setTextureData on it; in + // both cases, we update the base/max LOD to give shaders access to levels as they become + // available. Note that this can only expand the LOD range (never shrink it), and that + // users can override this range by calling setMinMaxLevels(). + updateTextureLodRange(d, int8_t(dstLevel)); + } + +#endif +} + +void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers, Handle dst, Viewport dstRect, Handle src, Viewport srcRect, SamplerMagFilter filter) { + + // Note: blitDEPRECATED is only used by Renderer::copyFrame + DEBUG_MARKER() UTILS_UNUSED_IN_RELEASE auto& gl = mContext; assert_invariant(!gl.isES2()); + ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0, + "blitDEPRECATED only supports COLOR0"); + + ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 && + dstRect.left >= 0 && dstRect.bottom >= 0, + "Source and destination rects must be positive."); + #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - GLbitfield const mask = getAttachmentBitfield(buffers); - if (mask) { - GLenum glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR; - if (mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) { - // GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or - // GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST. - glFilterMode = GL_NEAREST; - } - - // note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar - // buffer -- this should produce the same output that if we copied from the resolved - // texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this - // is an emulation of that. We cannot use the resolved texture easily because it's not - // actually attached to the RenderTarget. Another implementation would be to do a - // reverse-resolve, but that wouldn't buy us anything. - GLRenderTarget const* s = handle_cast(src); - GLRenderTarget const* d = handle_cast(dst); - - // blit operations are only supported from RenderTargets that have only COLOR0 (or - // no color buffer at all) - assert_invariant( - !(s->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0))); - - assert_invariant( - !(d->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0))); - - // With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS - // for the draw buffer is greater than zero. This works with OpenGL, so we want to - // make sure to catch this scenario. - assert_invariant(d->gl.samples <= 1); - - // GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater - // than zero and the formats of draw and read buffers are not identical. - // However, it's not well-defined in the spec what "format" means. So it's difficult - // to have an assert here -- especially when dealing with the default framebuffer - - // GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater - // than zero and (...) the source and destination rectangles are not defined with the - // same (X0, Y0) and (X1, Y1) bounds. - - // Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what - // happens when blitting from an "implicit" resolve render target (does it work?), so - // to ere on the safe side, we don't allow it. - if (s->gl.samples > 1) { - assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect))); - } - - gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo); - gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo); - CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER) - CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER) + GLenum const glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR; - gl.disable(GL_SCISSOR_TEST); - glBlitFramebuffer( - srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(), - dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(), - mask, glFilterMode); - CHECK_GL_ERROR(utils::slog.e) + // note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar + // buffer -- this should produce the same output that if we copied from the resolved + // texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this + // is an emulation of that. We cannot use the resolved texture easily because it's not + // actually attached to the RenderTarget. Another implementation would be to do a + // reverse-resolve, but that wouldn't buy us anything. + GLRenderTarget const* s = handle_cast(src); + GLRenderTarget const* d = handle_cast(dst); + + // With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS + // for the draw buffer is greater than zero. This works with OpenGL, so we want to + // make sure to catch this scenario. + assert_invariant(d->gl.samples <= 1); + + // GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater + // than zero and the formats of draw and read buffers are not identical. + // However, it's not well-defined in the spec what "format" means. So it's difficult + // to have an assert here -- especially when dealing with the default framebuffer + + // GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater + // than zero and (...) the source and destination rectangles are not defined with the + // same (X0, Y0) and (X1, Y1) bounds. + + // Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what + // happens when blitting from an "implicit" resolve render target (does it work?), so + // to ere on the safe side, we don't allow it. + if (s->gl.samples > 1) { + assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect))); } + + gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo); + gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo); + + CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER) + CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER) + + gl.disable(GL_SCISSOR_TEST); + glBlitFramebuffer( + srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(), + dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(), + GL_COLOR_BUFFER_BIT, glFilterMode); + CHECK_GL_ERROR(utils::slog.e) #endif } @@ -3492,52 +3807,52 @@ void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel) #endif } -void OpenGLDriver::draw(PipelineState state, Handle rph, uint32_t instanceCount) { +void OpenGLDriver::bindPipeline(PipelineState state) { DEBUG_MARKER() auto& gl = mContext; + setRasterState(state.rasterState); + setStencilState(state.stencilState); + gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant); + OpenGLProgram* const p = handle_cast(state.program); + mValidProgram = useProgram(p); +} - OpenGLProgram* p = handle_cast(state.program); - - // If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur - // during the draw call when the program is invalid. The shader compile error has already been - // dumped to the console at this point, so it's fine to simply return early. - if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) { - return; - } - - useProgram(p); +void OpenGLDriver::bindRenderPrimitive(Handle rph) { + DEBUG_MARKER() + auto& gl = mContext; - GLRenderPrimitive* rp = handle_cast(rph); + GLRenderPrimitive* const rp = handle_cast(rph); // Gracefully do nothing if the render primitive has not been set up. VertexBufferHandle vb = rp->gl.vertexBufferWithObjects; if (UTILS_UNLIKELY(!vb)) { + mBoundRenderPrimitive = nullptr; return; } - gl.bindVertexArray(&rp->gl); - // If necessary, mutate the bindings in the VAO. - const GLVertexBuffer* glvb = handle_cast(vb); - if (UTILS_UNLIKELY(rp->gl.vertexBufferVersion != glvb->bufferObjectsVersion)) { - updateVertexArrayObject(rp, glvb); - } - - setRasterState(state.rasterState); - setStencilState(state.stencilState); + gl.bindVertexArray(&rp->gl); + GLVertexBuffer const* const glvb = handle_cast(vb); + updateVertexArrayObject(rp, glvb); - gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant); + mBoundRenderPrimitive = rp; +} - setScissor(state.scissor); +void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { + GLRenderPrimitive const* const rp = mBoundRenderPrimitive; + if (UTILS_UNLIKELY(!rp || !mValidProgram)) { + return; + } if (UTILS_LIKELY(instanceCount <= 1)) { - glDrawElements(GLenum(rp->type), (GLsizei)rp->count, rp->gl.getIndicesType(), - reinterpret_cast(rp->offset)); + glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(), + reinterpret_cast(indexOffset * rp->gl.indicesSize)); } else { assert_invariant(!mContext.isES2()); #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - glDrawElementsInstanced(GLenum(rp->type), (GLsizei)rp->count, - rp->gl.getIndicesType(), reinterpret_cast(rp->offset), + glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount, + rp->gl.getIndicesType(), + reinterpret_cast(indexOffset * rp->gl.indicesSize), (GLsizei)instanceCount); #endif } @@ -3549,20 +3864,34 @@ void OpenGLDriver::draw(PipelineState state, Handle rph, uint #endif } +void OpenGLDriver::scissor(Viewport scissor) { + setScissor(scissor); +} + +void OpenGLDriver::draw(PipelineState state, Handle rph, + uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) { + DEBUG_MARKER() + GLRenderPrimitive* const rp = handle_cast(rph); + state.primitiveType = rp->type; + state.vertexBufferInfo = rp->vbih; + bindPipeline(state); + bindRenderPrimitive(rph); + draw2(indexOffset, indexCount, instanceCount); +} + void OpenGLDriver::dispatchCompute(Handle program, math::uint3 workGroupCount) { getShaderCompilerService().tick(); - OpenGLProgram* p = handle_cast(program); + OpenGLProgram* const p = handle_cast(program); - // If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur - // during the draw call when the program is invalid. The shader compile error has already been - // dumped to the console at this point, so it's fine to simply return early. - if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) { + bool const success = useProgram(p); + if (UTILS_UNLIKELY(!success)) { + // Avoid fatal (or cascading) errors that can occur during the draw call when the program + // is invalid. The shader compile error has already been dumped to the console at this + // point, so it's fine to simply return early. return; } - useProgram(p); - #if defined(BACKEND_OPENGL_LEVEL_GLES31) #if defined(__ANDROID__) diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index 1cf1be4d36f..06befbeffff 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -18,27 +18,44 @@ #define TNT_FILAMENT_BACKEND_OPENGL_OPENGLDRIVER_H #include "DriverBase.h" -#include "GLUtils.h" #include "OpenGLContext.h" +#include "OpenGLTimerQuery.h" +#include "GLBufferObject.h" +#include "GLTexture.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 #ifndef FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB # define FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB 4 @@ -51,15 +68,17 @@ class PixelBufferDescriptor; struct TargetBufferInfo; class OpenGLProgram; -class OpenGLTimerQueryInterface; +class TimerQueryFactoryInterface; class OpenGLDriver final : public DriverBase { - inline explicit OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept; + 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; @@ -75,29 +94,22 @@ class OpenGLDriver final : public DriverBase { bool rec709 = false; }; - struct GLBufferObject : public HwBufferObject { - using HwBufferObject::HwBufferObject; - GLBufferObject(uint32_t size, - BufferObjectBinding bindingType, BufferUsage usage) noexcept - : HwBufferObject(size), usage(usage), bindingType(bindingType) { + struct GLVertexBufferInfo : public HwVertexBufferInfo { + GLVertexBufferInfo() noexcept = default; + GLVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount, + AttributeArray const& attributes) + : HwVertexBufferInfo(bufferCount, attributeCount), + attributes(attributes) { } - - struct { - union { - struct { - GLuint id; - GLenum binding; - }; - void* buffer; - }; - } gl; - BufferUsage usage; - BufferObjectBinding bindingType; - uint16_t age = 0; + AttributeArray attributes; }; struct GLVertexBuffer : public HwVertexBuffer { - using HwVertexBuffer::HwVertexBuffer; + GLVertexBuffer() noexcept = default; + GLVertexBuffer(uint32_t vertexCount, Handle vbih) + : HwVertexBuffer(vertexCount), vbih(vbih) { + } + Handle vbih; struct { // 4 * MAX_VERTEX_ATTRIBUTE_COUNT bytes std::array buffers{}; @@ -111,7 +123,6 @@ class OpenGLDriver final : public DriverBase { } gl; }; - struct GLTexture; struct GLSamplerGroup : public HwSamplerGroup { using HwSamplerGroup::HwSamplerGroup; struct Entry { @@ -125,39 +136,14 @@ class OpenGLDriver final : public DriverBase { struct GLRenderPrimitive : public HwRenderPrimitive { using HwRenderPrimitive::HwRenderPrimitive; OpenGLContext::RenderPrimitive gl; + Handle vbih; }; - struct GLTexture : public HwTexture { - using HwTexture::HwTexture; - struct GL { - GL() noexcept : imported(false), sidecarSamples(1), reserved(0) {} - GLuint id = 0; // texture or renderbuffer id - GLenum target = 0; - GLenum internalFormat = 0; - GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer - - // texture parameters go here too - GLfloat anisotropy = 1.0; - int8_t baseLevel = 127; - int8_t maxLevel = -1; - uint8_t targetIndex = 0; // optimization: index corresponding to target - bool imported : 1; - uint8_t sidecarSamples : 4; - uint8_t reserved : 3; - } gl; + using GLBufferObject = filament::backend::GLBufferObject; - OpenGLPlatform::ExternalTexture* externalTexture = nullptr; - }; + using GLTexture = filament::backend::GLTexture; - struct GLTimerQuery : public HwTimerQuery { - struct State { - struct { - GLuint query; - } gl; - std::atomic elapsed{}; - }; - std::shared_ptr state; - }; + using GLTimerQuery = filament::backend::GLTimerQuery; struct GLStream : public HwStream { using HwStream::HwStream; @@ -211,8 +197,8 @@ class OpenGLDriver final : public DriverBase { OpenGLContext mContext; ShaderCompilerService mShaderCompilerService; - friend class OpenGLTimerQueryFactory; - friend class TimerQueryNative; + friend class TimerQueryFactory; + friend class TimerQueryNativeFactory; OpenGLContext& getContext() noexcept { return mContext; } ShaderCompilerService& getShaderCompilerService() noexcept { @@ -246,13 +232,13 @@ class OpenGLDriver final : public DriverBase { HandleAllocatorGL mHandleAllocator; template - Handle initHandle(ARGS&& ... args) noexcept { + Handle initHandle(ARGS&& ... args) { return mHandleAllocator.allocateAndConstruct(std::forward(args) ...); } template typename std::enable_if::value, D>::type* - construct(Handle const& handle, ARGS&& ... args) noexcept { + construct(Handle const& handle, ARGS&& ... args) { return mHandleAllocator.destroyAndConstruct(handle, std::forward(args) ...); } @@ -266,7 +252,7 @@ class OpenGLDriver final : public DriverBase { typename std::enable_if_t< std::is_pointer_v && std::is_base_of_v>, Dp> - handle_cast(Handle& handle) noexcept { + handle_cast(Handle& handle) { return mHandleAllocator.handle_cast(handle); } @@ -274,7 +260,7 @@ class OpenGLDriver final : public DriverBase { inline typename std::enable_if_t< std::is_pointer_v && std::is_base_of_v>, Dp> - handle_cast(Handle const& handle) noexcept { + handle_cast(Handle const& handle) { return mHandleAllocator.handle_cast(handle); } @@ -292,7 +278,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; @@ -313,42 +299,26 @@ class OpenGLDriver final : public DriverBase { void renderBufferStorage(GLuint rbo, GLenum internalformat, uint32_t width, uint32_t height, uint8_t samples) const noexcept; - void textureStorage(GLTexture* t, - uint32_t width, uint32_t height, uint32_t depth) noexcept; + void textureStorage(OpenGLDriver::GLTexture* t, uint32_t width, uint32_t height, + uint32_t depth, bool useProtectedMemory) noexcept; /* State tracking GL wrappers... */ void bindTexture(GLuint unit, GLTexture const* t) noexcept; void bindSampler(GLuint unit, GLuint sampler) noexcept; - inline void useProgram(OpenGLProgram* p) noexcept; + inline bool useProgram(OpenGLProgram* p) noexcept; enum class ResolveAction { LOAD, STORE }; void resolvePass(ResolveAction action, GLRenderTarget const* rt, TargetBufferFlags discardFlags) noexcept; -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - GLuint getSamplerSlow(SamplerParams sp) const noexcept; - - inline GLuint getSampler(SamplerParams sp) const noexcept { - assert_invariant(!sp.padding0); - assert_invariant(!sp.padding1); - assert_invariant(!sp.padding2); - auto& samplerMap = mSamplerMap; - auto pos = samplerMap.find(sp); - if (UTILS_UNLIKELY(pos == samplerMap.end())) { - return getSamplerSlow(sp); - } - return pos->second; - } -#endif - const std::array& getSamplerBindings() const { return mSamplerBindings; } 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; @@ -357,20 +327,21 @@ class OpenGLDriver final : public DriverBase { GLboolean mRenderPassDepthWrite{}; GLboolean mRenderPassStencilWrite{}; + GLRenderPrimitive const* mBoundRenderPrimitive = nullptr; + bool mValidProgram = false; + + void clearWithRasterPipe(TargetBufferFlags clearFlags, math::float4 const& linearColor, GLfloat depth, GLint stencil) noexcept; void setScissor(Viewport const& scissor) noexcept; // ES2 only. Uniform buffer emulation binding points - std::array, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {}; + GLuint mLastAssignedEmulatedUboId = 0; // sampler buffer binding points (nullptr if not used) std::array mSamplerBindings = {}; // 4 pointers - mutable tsl::robin_map mSamplerMap; - // this must be accessed from the driver thread only std::vector mTexturesWithStreamsAttached; @@ -398,8 +369,8 @@ class OpenGLDriver final : public DriverBase { void executeEveryNowAndThenOps() noexcept; std::vector> mEveryNowAndThenOps; - // timer query implementation - OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr; + const Platform::DriverConfig mDriverConfig; + Platform::DriverConfig const& getDriverConfig() const noexcept { return mDriverConfig; } // for ES2 sRGB support GLSwapChain* mCurrentDrawSwapChain = nullptr; diff --git a/filament/backend/src/opengl/OpenGLPlatform.cpp b/filament/backend/src/opengl/OpenGLPlatform.cpp index 837faecdc4c..17359e85f7b 100644 --- a/filament/backend/src/opengl/OpenGLPlatform.cpp +++ b/filament/backend/src/opengl/OpenGLPlatform.cpp @@ -18,6 +18,17 @@ #include "OpenGLDriverFactory.h" +#include +#include +#include + +#include + +#include +#include +#include +#include + namespace filament::backend { Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform, @@ -27,14 +38,27 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform, OpenGLPlatform::~OpenGLPlatform() noexcept = default; +void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain, + utils::Invocable, utils::Invocable) noexcept { + makeCurrent(getCurrentContextType(), drawSwapChain, readSwapChain); +} + +bool OpenGLPlatform::isProtectedContextSupported() const noexcept { + return false; +} + 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 { } @@ -105,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 e699149d1da..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 { @@ -129,7 +138,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra #endif { // ES2 initialization of (fake) UBOs - UniformsRecord* const uniformsRecords = new UniformsRecord[Program::UNIFORM_BINDING_COUNT]; + UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT]; UTILS_NOUNROLL for (GLuint binding = 0, n = Program::UNIFORM_BINDING_COUNT; binding < n; binding++) { Program::UniformInfo& uniforms = lazyInitializationData.bindingUniformInfo[binding]; @@ -212,6 +221,7 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept { assert_invariant(binding < Program::SAMPLER_BINDING_COUNT); auto const * const sb = samplerBindings[binding]; assert_invariant(sb); + if (!sb) continue; // should never happen, this would be a user error. for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here const GLTexture* const t = sb->textureUnitEntries[j].texture; if (t) { // program may not use all samplers of sampler group @@ -228,15 +238,16 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept { CHECK_GL_ERROR(utils::slog.e) } -void OpenGLProgram::updateUniforms(uint32_t index, void const* buffer, uint16_t age) noexcept { +void OpenGLProgram::updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept { assert_invariant(mUniformsRecords); assert_invariant(buffer); // only update the uniforms if the UBO has changed since last time we updated UniformsRecord const& records = mUniformsRecords[index]; - if (records.age == age) { + if (records.id == id && records.age == age) { return; } + records.id = id; records.age = age; assert_invariant(records.uniforms.size() == records.locations.size()); diff --git a/filament/backend/src/opengl/OpenGLProgram.h b/filament/backend/src/opengl/OpenGLProgram.h index 5d183f6da1c..8b9d2c400b5 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 @@ -59,7 +62,7 @@ class OpenGLProgram : public HwProgram { // - the content of any bound sampler buffer has changed // ... since last time we used this program - // turns out the former might be relatively cheap to check, the later requires + // Turns out the former might be relatively cheap to check, the latter requires // a bit less. Compared to what updateSamplers() actually does, which is // pretty little, I'm not sure if we'll get ahead. @@ -67,14 +70,14 @@ class OpenGLProgram : public HwProgram { } } - struct { - GLuint program = 0; - } gl; // 12 bytes - // For ES2 only - void updateUniforms(uint32_t index, void const* buffer, uint16_t age) noexcept; + void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept; void setRec709ColorSpace(bool rec709) const noexcept; + struct { + GLuint program = 0; + } gl; // 4 bytes + private: // keep these away from of other class attributes struct LazyInitializationData; @@ -86,26 +89,29 @@ class OpenGLProgram : public HwProgram { void updateSamplers(OpenGLDriver* gld) const noexcept; - ShaderCompilerService::program_token_t mToken{}; - // number of bindings actually used by this program - uint8_t mUsedBindingsCount = 0u; - UTILS_UNUSED uint8_t padding[3] = {}; std::array mUsedSamplerBindingPoints; // 4 bytes + ShaderCompilerService::program_token_t mToken{}; // 16 bytes + + uint8_t mUsedBindingsCount = 0u; // 1 byte + UTILS_UNUSED uint8_t padding[3] = {}; // 3 bytes + + // only needed for ES2 + GLint mRec709Location = -1; // 4 bytes using LocationInfo = utils::FixedCapacityVector; struct UniformsRecord { Program::UniformInfo uniforms; LocationInfo locations; + mutable GLuint id = 0; mutable uint16_t age = std::numeric_limits::max(); }; UniformsRecord const* mUniformsRecords = nullptr; - GLint mRec709Location = -1; }; // if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket. -static_assert(sizeof(OpenGLProgram) <= 64); +static_assert(sizeof(OpenGLProgram) <= 64); // currently 48 bytes } // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.cpp b/filament/backend/src/opengl/OpenGLTimerQuery.cpp index 6acb9322111..83a9d834c1e 100644 --- a/filament/backend/src/opengl/OpenGLTimerQuery.cpp +++ b/filament/backend/src/opengl/OpenGLTimerQuery.cpp @@ -16,110 +16,132 @@ #include "OpenGLTimerQuery.h" +#include "GLUtils.h" +#include "OpenGLDriver.h" + +#include #include +#include #include +#include #include #include +#include #include -#include + +#include +#include +#include +#include +#include + +#include namespace filament::backend { using namespace backend; using namespace GLUtils; +class OpenGLDriver; + // ------------------------------------------------------------------------------------------------ -bool OpenGLTimerQueryFactory::mGpuTimeSupported = false; +bool TimerQueryFactory::mGpuTimeSupported = false; -OpenGLTimerQueryInterface* OpenGLTimerQueryFactory::init( - OpenGLPlatform& platform, OpenGLDriver& driver) noexcept { - (void)driver; +TimerQueryFactoryInterface* TimerQueryFactory::init( + OpenGLPlatform& platform, OpenGLContext& context) noexcept { + (void)context; - OpenGLTimerQueryInterface* impl; + TimerQueryFactoryInterface* impl = nullptr; #if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query) - auto& context = driver.getContext(); if (context.ext.EXT_disjoint_timer_query) { // timer queries are available if (context.bugs.dont_use_timer_query && platform.canCreateFence()) { // however, they don't work well, revert to using fences if we can. - impl = new(std::nothrow) OpenGLTimerQueryFence(platform); + impl = new(std::nothrow) TimerQueryFenceFactory(platform); } else { - impl = new(std::nothrow) TimerQueryNative(driver); + impl = new(std::nothrow) TimerQueryNativeFactory(context); } mGpuTimeSupported = true; } else #endif if (platform.canCreateFence()) { // no timer queries, but we can use fences - impl = new(std::nothrow) OpenGLTimerQueryFence(platform); + impl = new(std::nothrow) TimerQueryFenceFactory(platform); mGpuTimeSupported = true; } else { // no queries, no fences -- that's a problem - impl = new(std::nothrow) TimerQueryFallback(); + impl = new(std::nothrow) TimerQueryFallbackFactory(); mGpuTimeSupported = false; } + assert_invariant(impl); return impl; } // ------------------------------------------------------------------------------------------------ -OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default; +TimerQueryFactoryInterface::~TimerQueryFactoryInterface() = default; // This is a backend synchronous call -bool OpenGLTimerQueryInterface::getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept { +TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue( + GLTimerQuery* tq, uint64_t* elapsedTime) noexcept { if (UTILS_LIKELY(tq->state)) { int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed); - bool const available = elapsed > 0; - if (available) { + if (elapsed > 0) { *elapsedTime = elapsed; + return TimerQueryResult::AVAILABLE; } - return available; + return TimerQueryResult(elapsed); } - return false; + return TimerQueryResult::ERROR; } // ------------------------------------------------------------------------------------------------ #if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query) -TimerQueryNative::TimerQueryNative(OpenGLDriver& driver) - : mDriver(driver) { +TimerQueryNativeFactory::TimerQueryNativeFactory(OpenGLContext& context) + : mContext(context) { } -TimerQueryNative::~TimerQueryNative() = default; +TimerQueryNativeFactory::~TimerQueryNativeFactory() = default; -void TimerQueryNative::createTimerQuery(GLTimerQuery* tq) { - if (UTILS_UNLIKELY(!tq->state)) { - tq->state = std::make_shared(); - } - mDriver.getContext().procs.genQueries(1u, &tq->state->gl.query); +void TimerQueryNativeFactory::createTimerQuery(GLTimerQuery* tq) { + assert_invariant(!tq->state); + + tq->state = std::make_shared(); + mContext.procs.genQueries(1u, &tq->state->gl.query); CHECK_GL_ERROR(utils::slog.e) } -void TimerQueryNative::destroyTimerQuery(GLTimerQuery* tq) { +void TimerQueryNativeFactory::destroyTimerQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - mDriver.getContext().procs.deleteQueries(1u, &tq->state->gl.query); + + mContext.procs.deleteQueries(1u, &tq->state->gl.query); CHECK_GL_ERROR(utils::slog.e) + + tq->state.reset(); } -void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryNativeFactory::beginTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - tq->state->elapsed.store(0); - mDriver.getContext().procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query); + + tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed); + mContext.procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query); CHECK_GL_ERROR(utils::slog.e) } -void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* tq) { assert_invariant(tq->state); - mDriver.getContext().procs.endQuery(GL_TIME_ELAPSED); + + mContext.procs.endQuery(GL_TIME_ELAPSED); CHECK_GL_ERROR(utils::slog.e) std::weak_ptr const weak = tq->state; - mDriver.runEveryNowAndThen([context = mDriver.getContext(), weak]() -> bool { + driver.runEveryNowAndThen([&context = mContext, weak]() -> bool { auto state = weak.lock(); if (state) { GLuint available = 0; @@ -133,6 +155,8 @@ void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) { // we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime); state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed); + } else { + state->elapsed.store(int64_t(TimerQueryResult::ERROR), std::memory_order_relaxed); } return true; }); @@ -142,7 +166,7 @@ void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) { // ------------------------------------------------------------------------------------------------ -OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform) +TimerQueryFenceFactory::TimerQueryFenceFactory(OpenGLPlatform& platform) : mPlatform(platform) { mQueue.reserve(2); mThread = std::thread([this]() { @@ -166,7 +190,8 @@ OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform) }); } -OpenGLTimerQueryFence::~OpenGLTimerQueryFence() { +TimerQueryFenceFactory::~TimerQueryFenceFactory() { + assert_invariant(mQueue.empty()); if (mThread.joinable()) { std::unique_lock lock(mLock); mExitRequested = true; @@ -178,27 +203,26 @@ OpenGLTimerQueryFence::~OpenGLTimerQueryFence() { } } -void OpenGLTimerQueryFence::enqueue(OpenGLTimerQueryFence::Job&& job) { +void TimerQueryFenceFactory::push(TimerQueryFenceFactory::Job&& job) { std::unique_lock const lock(mLock); - mQueue.push_back(std::forward(job)); + mQueue.push_back(std::move(job)); mCondition.notify_one(); } -void OpenGLTimerQueryFence::createTimerQuery(GLTimerQuery* tq) { - if (UTILS_UNLIKELY(!tq->state)) { - tq->state = std::make_shared(); - } +void TimerQueryFenceFactory::createTimerQuery(GLTimerQuery* tq) { + assert_invariant(!tq->state); + tq->state = std::make_shared(); } -void OpenGLTimerQueryFence::destroyTimerQuery(GLTimerQuery* tq) { +void TimerQueryFenceFactory::destroyTimerQuery(GLTimerQuery* tq) { assert_invariant(tq->state); + tq->state.reset(); } -void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryFenceFactory::beginTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - tq->state->elapsed.store(0); + tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed); - Platform::Fence* fence = mPlatform.createFence(); std::weak_ptr const weak = tq->state; // FIXME: this implementation of beginTimeElapsedQuery is usually wrong; it ends up @@ -207,12 +231,11 @@ void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) { // on a dummy target for instance, or somehow latch the begin time at the next renderpass // start. - push([&platform = mPlatform, fence, weak]() { + push([&platform = mPlatform, fence = mPlatform.createFence(), weak]() { auto state = weak.lock(); if (state) { platform.waitFence(fence, FENCE_WAIT_FOR_EVER); - int64_t const then = clock::now().time_since_epoch().count(); - state->elapsed.store(-then, std::memory_order_relaxed); + state->then = clock::now().time_since_epoch().count(); SYSTRACE_CONTEXT(); SYSTRACE_ASYNC_BEGIN("OpenGLTimerQueryFence", intptr_t(state.get())); } @@ -220,19 +243,16 @@ void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) { }); } -void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryFenceFactory::endTimeElapsedQuery(OpenGLDriver&, GLTimerQuery* tq) { assert_invariant(tq->state); - Platform::Fence* fence = mPlatform.createFence(); std::weak_ptr const weak = tq->state; - push([&platform = mPlatform, fence, weak]() { + push([&platform = mPlatform, fence = mPlatform.createFence(), weak]() { auto state = weak.lock(); if (state) { platform.waitFence(fence, FENCE_WAIT_FOR_EVER); int64_t const now = clock::now().time_since_epoch().count(); - int64_t const then = state->elapsed.load(std::memory_order_relaxed); - assert_invariant(then < 0); - state->elapsed.store(now + then, std::memory_order_relaxed); + state->elapsed.store(now - state->then, std::memory_order_relaxed); SYSTRACE_CONTEXT(); SYSTRACE_ASYNC_END("OpenGLTimerQueryFence", intptr_t(state.get())); } @@ -242,34 +262,32 @@ void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* tq) { // ------------------------------------------------------------------------------------------------ -TimerQueryFallback::TimerQueryFallback() = default; +TimerQueryFallbackFactory::TimerQueryFallbackFactory() = default; -TimerQueryFallback::~TimerQueryFallback() = default; +TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default; -void TimerQueryFallback::createTimerQuery(GLTimerQuery* tq) { - if (UTILS_UNLIKELY(!tq->state)) { - tq->state = std::make_shared(); - } +void TimerQueryFallbackFactory::createTimerQuery(GLTimerQuery* tq) { + assert_invariant(!tq->state); + tq->state = std::make_shared(); } -void TimerQueryFallback::destroyTimerQuery(GLTimerQuery* tq) { +void TimerQueryFallbackFactory::destroyTimerQuery(GLTimerQuery* tq) { assert_invariant(tq->state); + tq->state.reset(); } -void TimerQueryFallback::beginTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) { +void TimerQueryFallbackFactory::beginTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); // this implementation measures the CPU time, but we have no h/w support - int64_t const then = clock::now().time_since_epoch().count(); - tq->state->elapsed.store(-then, std::memory_order_relaxed); + tq->state->then = clock::now().time_since_epoch().count(); + tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed); } -void TimerQueryFallback::endTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) { +void TimerQueryFallbackFactory::endTimeElapsedQuery(OpenGLDriver&, GLTimerQuery* tq) { assert_invariant(tq->state); // this implementation measures the CPU time, but we have no h/w support int64_t const now = clock::now().time_since_epoch().count(); - int64_t const then = tq->state->elapsed.load(std::memory_order_relaxed); - assert_invariant(then < 0); - tq->state->elapsed.store(now + then, std::memory_order_relaxed); + tq->state->elapsed.store(now - tq->state->then, std::memory_order_relaxed); } } // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.h b/filament/backend/src/opengl/OpenGLTimerQuery.h index 5a42c290822..5941c87d2b6 100644 --- a/filament/backend/src/opengl/OpenGLTimerQuery.h +++ b/filament/backend/src/opengl/OpenGLTimerQuery.h @@ -17,18 +17,41 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_TIMERQUERY_H #define TNT_FILAMENT_BACKEND_OPENGL_TIMERQUERY_H -#include "OpenGLDriver.h" +#include + +#include "DriverBase.h" #include #include +#include "gl_headers.h" + +#include +#include +#include +#include #include #include +#include + namespace filament::backend { class OpenGLPlatform; -class OpenGLTimerQueryInterface; +class OpenGLContext; +class OpenGLDriver; +class TimerQueryFactoryInterface; + +struct GLTimerQuery : public HwTimerQuery { + struct State { + struct { + GLuint query; + } gl; + int64_t then{}; + std::atomic elapsed{}; + }; + std::shared_ptr state; +}; /* * We need two implementation of timer queries (only elapsed time), because @@ -38,83 +61,80 @@ class OpenGLTimerQueryInterface; * These classes implement the various strategies... */ - -class OpenGLTimerQueryFactory { +class TimerQueryFactory { static bool mGpuTimeSupported; public: - static OpenGLTimerQueryInterface* init( - OpenGLPlatform& platform, OpenGLDriver& driver) noexcept; + static TimerQueryFactoryInterface* init( + OpenGLPlatform& platform, OpenGLContext& context) noexcept; static bool isGpuTimeSupported() noexcept { return mGpuTimeSupported; } }; -class OpenGLTimerQueryInterface { +class TimerQueryFactoryInterface { protected: - using GLTimerQuery = OpenGLDriver::GLTimerQuery; + using GLTimerQuery = filament::backend::GLTimerQuery; using clock = std::chrono::steady_clock; public: - virtual ~OpenGLTimerQueryInterface(); + virtual ~TimerQueryFactoryInterface(); virtual void createTimerQuery(GLTimerQuery* query) = 0; virtual void destroyTimerQuery(GLTimerQuery* query) = 0; virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0; - virtual void endTimeElapsedQuery(GLTimerQuery* query) = 0; + virtual void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) = 0; - static bool getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept; + static TimerQueryResult getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept; }; #if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query) -class TimerQueryNative : public OpenGLTimerQueryInterface { +class TimerQueryNativeFactory final : public TimerQueryFactoryInterface { public: - explicit TimerQueryNative(OpenGLDriver& driver); - ~TimerQueryNative() override; + explicit TimerQueryNativeFactory(OpenGLContext& context); + ~TimerQueryNativeFactory() override; private: void createTimerQuery(GLTimerQuery* query) override; void destroyTimerQuery(GLTimerQuery* query) override; void beginTimeElapsedQuery(GLTimerQuery* query) override; - void endTimeElapsedQuery(GLTimerQuery* query) override; - OpenGLDriver& mDriver; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; + OpenGLContext& mContext; }; #endif -class OpenGLTimerQueryFence : public OpenGLTimerQueryInterface { +class TimerQueryFenceFactory final : public TimerQueryFactoryInterface { public: - explicit OpenGLTimerQueryFence(OpenGLPlatform& platform); - ~OpenGLTimerQueryFence() override; + explicit TimerQueryFenceFactory(OpenGLPlatform& platform); + ~TimerQueryFenceFactory() override; private: using Job = std::function; + using Container = std::vector; + void createTimerQuery(GLTimerQuery* query) override; void destroyTimerQuery(GLTimerQuery* query) override; void beginTimeElapsedQuery(GLTimerQuery* tq) override; - void endTimeElapsedQuery(GLTimerQuery* tq) override; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* tq) override; - void enqueue(Job&& job); - template - void push(CALLABLE&& func, ARGS&& ... args) { - enqueue(Job(std::bind(std::forward(func), std::forward(args)...))); - } + void push(Job&& job); OpenGLPlatform& mPlatform; std::thread mThread; mutable utils::Mutex mLock; mutable utils::Condition mCondition; - std::vector mQueue; + Container mQueue; bool mExitRequested = false; }; -class TimerQueryFallback : public OpenGLTimerQueryInterface { +class TimerQueryFallbackFactory final : public TimerQueryFactoryInterface { public: - explicit TimerQueryFallback(); - ~TimerQueryFallback() override; + explicit TimerQueryFallbackFactory(); + ~TimerQueryFallbackFactory() override; private: void createTimerQuery(GLTimerQuery* query) override; void destroyTimerQuery(GLTimerQuery* query) override; void beginTimeElapsedQuery(GLTimerQuery* query) override; - void endTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; }; } // namespace filament::backend diff --git a/filament/backend/src/opengl/ShaderCompilerService.cpp b/filament/backend/src/opengl/ShaderCompilerService.cpp index ad6914d2f6f..5b9397ddd4b 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.cpp +++ b/filament/backend/src/opengl/ShaderCompilerService.cpp @@ -22,7 +22,6 @@ #include -#include #include #include @@ -31,6 +30,7 @@ #include #include +#include #include #include #include @@ -140,20 +140,44 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver) : mDriver(driver), - mCallbackManager(driver), - KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) { + mBlobCache(driver.getContext()), + mCallbackManager(driver) { } ShaderCompilerService::~ShaderCompilerService() noexcept = default; bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept { - return KHR_parallel_shader_compile || mShaderCompilerThreadCount; + assert_invariant(mMode != Mode::UNDEFINED); + return mMode != Mode::SYNCHRONOUS; } void ShaderCompilerService::init() noexcept { - // If we have KHR_parallel_shader_compile, we always use it, it should be more resource - // friendly. - if (!KHR_parallel_shader_compile) { + if (UTILS_UNLIKELY(mDriver.getDriverConfig().disableParallelShaderCompile)) { + // user disabled parallel shader compile + mMode = Mode::SYNCHRONOUS; + return; + } + + // Here we decide which mode we'll be using. We always prefer our own thread-pool if + // that mode is available because, we have no control on how the compilation queues are + // handled if done by the driver (so at the very least we'd need to decode this per-driver). + // In theory, using Mode::ASYNCHRONOUS (a.k.a. KHR_parallel_shader_compile) could be more + // efficient, since it doesn't require shared contexts. + // In practice, we already know that with ANGLE, Mode::ASYNCHRONOUS can cause very long + // pauses at glDraw() time in the situation where glLinkProgram() has been emitted, but has + // other programs ahead of it in ANGLE's queue. + if (mDriver.mPlatform.isExtraContextSupported()) { + // our thread-pool if possible + mMode = Mode::THREAD_POOL; + } else if (mDriver.getContext().ext.KHR_parallel_shader_compile) { + // if not, async shader compilation and link if the driver supports it + mMode = Mode::ASYNCHRONOUS; + } else { + // fallback to synchronous shader compilation + mMode = Mode::SYNCHRONOUS; + } + + if (mMode == Mode::THREAD_POOL) { // - on Adreno there is a single compiler object. We can't use a pool > 1 // also glProgramBinary blocks if other threads are compiling. // - on Mali shader compilation can be multi-threaded, but program linking happens on @@ -163,37 +187,42 @@ void ShaderCompilerService::init() noexcept { // - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using // a shared context, so parallel shader compilation yields no benefit. // - on windows/linux we could use more threads, tbd. - if (mDriver.mPlatform.isExtraContextSupported()) { - // By default, we use one thread at the same priority as the gl thread. This is the - // safest choice that avoids priority inversions. - uint32_t poolSize = 1; - JobSystem::Priority priority = JobSystem::Priority::DISPLAY; - - auto const& renderer = mDriver.getContext().state.renderer; - if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) { - // The PowerVR driver support parallel shader compilation well, so we use 2 - // threads, we can use lower priority threads here because urgent compilations - // will most likely happen on the main gl thread. Using too many thread can - // increase memory pressure significantly. - poolSize = 2; - priority = JobSystem::Priority::BACKGROUND; - } - mShaderCompilerThreadCount = poolSize; - mCompilerThreadPool.init(mShaderCompilerThreadCount, - [&platform = mDriver.mPlatform, priority]() { - // give the thread a name - JobSystem::setThreadName("CompilerThreadPool"); - // run at a slightly lower priority than other filament threads - JobSystem::setThreadPriority(priority); - // create a gl context current to this thread - platform.createContext(true); - }, - [&platform = mDriver.mPlatform]() { - // release context and thread state - platform.releaseContext(); - }); + // By default, we use one thread at the same priority as the gl thread. This is the + // safest choice that avoids priority inversions. + uint32_t poolSize = 1; + JobSystem::Priority priority = JobSystem::Priority::DISPLAY; + + auto const& renderer = mDriver.getContext().state.renderer; + // Some drivers support parallel shader compilation well, so we use N + // threads, we can use lower priority threads here because urgent compilations + // will most likely happen on the main gl thread. Using too many thread can + // increase memory pressure significantly. + if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) { + // For PowerVR, it's unclear what the cost of extra shared contexts is, so we only + // use 2 to be safe. + poolSize = 2; + priority = JobSystem::Priority::BACKGROUND; + } else if (UTILS_UNLIKELY(strstr(renderer, "ANGLE"))) { + // Angle shared contexts are not expensive once we have two. + poolSize = (std::thread::hardware_concurrency() + 1) / 2; + priority = JobSystem::Priority::BACKGROUND; } + + mShaderCompilerThreadCount = poolSize; + mCompilerThreadPool.init(mShaderCompilerThreadCount, + [&platform = mDriver.mPlatform, priority]() { + // give the thread a name + JobSystem::setThreadName("CompilerThreadPool"); + // run at a slightly lower priority than other filament threads + JobSystem::setThreadPriority(priority); + // create a gl context current to this thread + platform.createContext(true); + }, + [&platform = mDriver.mPlatform]() { + // release context and thread state + platform.releaseContext(); + }); } } @@ -219,7 +248,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( token->attributes = std::move(program.getAttributes()); } - token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program); + token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program); if (token->gl.program) { return token; } @@ -227,18 +256,18 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( token->handle = mCallbackManager.get(); CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); - if (mShaderCompilerThreadCount) { + if (mMode == Mode::THREAD_POOL) { // queue a compile job mCompilerThreadPool.queue(priorityQueue, token, [this, &gl, program = std::move(program), token]() mutable { // compile the shaders std::array shaders{}; - std::array shaderSourceCode; compileShaders(gl, std::move(program.getShadersSource()), program.getSpecializationConstants(), + program.isMultiview(), shaders, - shaderSourceCode); + token->shaderSourceCode); // link the program GLuint const glProgram = linkProgram(gl, shaders, token->attributes); @@ -249,12 +278,10 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( // We need to query the link status here to guarantee that the // program is compiled and linked now (we don't want this to be // deferred to later). We don't care about the result at this point. - GLint status; + GLint status = GL_FALSE; glGetProgramiv(glProgram, GL_LINK_STATUS, &status); programData.program = glProgram; - token->gl.program = programData.program; - // we don't need to check for success here, it'll be done on the // main thread side. token->set(programData); @@ -262,9 +289,9 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( mCallbackManager.put(token->handle); // caching must be the last thing we do - if (token->key) { + if (token->key && status == GL_TRUE) { // Attempt to cache. This calls glGetProgramBinary. - OpenGLBlobCache::insert(mDriver.mPlatform, token->key, glProgram); + mBlobCache.insert(mDriver.mPlatform, token->key, glProgram); } }); @@ -274,11 +301,13 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( compileShaders(gl, std::move(program.getShadersSource()), program.getSpecializationConstants(), + program.isMultiview(), token->gl.shaders, token->shaderSourceCode); runAtNextTick(priorityQueue, token, [this, token](Job const&) { - if (KHR_parallel_shader_compile) { + assert_invariant(mMode != Mode::THREAD_POOL); + if (mMode == Mode::ASYNCHRONOUS) { // don't attempt to link this program if all shaders are not done compiling GLint status; if (token->gl.program) { @@ -302,7 +331,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( // link the program, this also cannot fail because status is checked later. token->gl.program = linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes); - if (KHR_parallel_shader_compile) { + if (mMode == Mode::ASYNCHRONOUS) { // wait until the link finishes... return false; } @@ -317,7 +346,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( // do this later, maybe depending on CPU usage? // attempt to cache if we don't have a thread pool (otherwise it's done // by the pool). - OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program); + mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program); } return true; @@ -336,14 +365,14 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& return program; } -/* static*/ void ShaderCompilerService::terminate(program_token_t& token) { +void ShaderCompilerService::terminate(program_token_t& token) { assert_invariant(token); token->canceled = true; - bool canceled = token->compiler.cancelTickOp(token); + bool const canceled = token->compiler.cancelTickOp(token); - if (token->compiler.mShaderCompilerThreadCount) { + if (token->compiler.mMode == Mode::THREAD_POOL) { auto job = token->compiler.mCompilerThreadPool.dequeue(token); if (!job) { // The job is being executed right now. We need to wait for it to finish to avoid a @@ -377,7 +406,7 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& void ShaderCompilerService::tick() { // we don't need to run executeTickOps() if we're using the thread-pool - if (UTILS_UNLIKELY(!mShaderCompilerThreadCount)) { + if (UTILS_UNLIKELY(mMode != Mode::THREAD_POOL)) { executeTickOps(); } } @@ -402,7 +431,7 @@ void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) n GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept { SYSTRACE_CALL(); if (!token->gl.program) { - if (mShaderCompilerThreadCount) { + if (mMode == Mode::THREAD_POOL) { // we need this program right now, remove it from the queue auto job = mCompilerThreadPool.dequeue(token); if (job) { @@ -418,7 +447,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept { // Block until we get the program from the pool. Generally this wouldn't block // because we just compiled the program above, when executing job. ShaderCompilerService::getProgramFromCompilerPool(token); - } else if (KHR_parallel_shader_compile) { + } else if (mMode == Mode::ASYNCHRONOUS) { // we force the program link -- which might stall, either here or below in // checkProgramStatus(), but we don't have a choice, we need to use the program now. token->compiler.cancelTickOp(token); @@ -431,7 +460,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept { mCallbackManager.put(token->handle); if (token->key) { - OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program); + mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program); } } else { // if we don't have a program yet, block until we get it. @@ -475,6 +504,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept { void ShaderCompilerService::compileShaders(OpenGLContext& context, Program::ShaderSource shadersSource, utils::FixedCapacityVector const& specializationConstants, + bool multiview, std::array& outShaders, UTILS_UNUSED_IN_RELEASE std::array& outShaderSourceCode) noexcept { @@ -488,8 +518,16 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context, }; std::string specializationConstantString; + int32_t numViews = 2; for (auto const& sc : specializationConstants) { appendSpecConstantString(specializationConstantString, sc); + if (sc.id == 8) { + // This constant must match + // ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT + // which we can't use here because it's defined in EngineEnums.h. + // (we're breaking layering here, but it's for the good cause). + numViews = std::get(sc.value); + } } if (!specializationConstantString.empty()) { specializationConstantString += '\n'; @@ -518,16 +556,23 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context, if (UTILS_LIKELY(!shadersSource[i].empty())) { Program::ShaderBlob& shader = shadersSource[i]; + char* shader_src = reinterpret_cast(shader.data()); + size_t shader_len = shader.size(); // remove GOOGLE_cpp_style_line_directive - std::string_view const source = process_GOOGLE_cpp_style_line_directive(context, - reinterpret_cast(shader.data()), shader.size()); + process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len); + + // replace the value of layout(num_views = X) for multiview extension + if (multiview && stage == ShaderStage::VERTEX) { + process_OVR_multiview2(context, numViews, shader_src, shader_len); + } // add support for ARB_shading_language_packing if needed auto const packingFunctions = process_ARB_shading_language_packing(context); - // split shader source, so we can insert the specification constants and the packing functions - auto const [prolog, body] = splitShaderSource(source); + // split shader source, so we can insert the specialization constants and the packing + // functions + auto const [prolog, body] = splitShaderSource({ shader_src, shader_len }); const std::array sources = { prolog.data(), @@ -550,7 +595,7 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context, #ifndef NDEBUG // for debugging we return the original shader source (without the modifications we // made here), otherwise the line numbers wouldn't match. - outShaderSourceCode[i] = { source.data(), source.length() }; + outShaderSourceCode[i] = { shader_src, shader_len }; #endif outShaders[i] = shaderId; @@ -559,15 +604,59 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context, } // If usages of the Google-style line directive are present, remove them, as some -// drivers don't allow the quotation marks. This happens in-place. -std::string_view ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, +// drivers don't allow the quotation marks. This source modification happens in-place. +void ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source, size_t len) noexcept { if (!context.ext.GOOGLE_cpp_style_line_directive) { if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) { removeGoogleLineDirectives(source, len); // length is unaffected } } - return { source, len }; +} + +// Look up the `source` to replace the number of eyes for multiview with the given number. This is +// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine +// the number of views, which is assumed as a single digit, for multiview. +// This source modification happens in-place. +void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context, + int32_t eyeCount, char* source, size_t len) noexcept { + // We don't use regular expression in favor of performance. + if (context.ext.OVR_multiview2) { + const std::string_view shader{ source, len }; + const std::string_view layout = "layout"; + const std::string_view num_views = "num_views"; + size_t found = 0; + while (true) { + found = shader.find(layout, found); + if (found == std::string_view::npos) { + break; + } + found = shader.find_first_not_of(' ', found + layout.size()); + if (found == std::string_view::npos || shader[found] != '(') { + continue; + } + found = shader.find_first_not_of(' ', found + 1); + if (found == std::string_view::npos) { + continue; + } + if (shader.compare(found, num_views.size(), num_views) != 0) { + continue; + } + found = shader.find_first_not_of(' ', found + num_views.size()); + if (found == std::string_view::npos || shader[found] != '=') { + continue; + } + found = shader.find_first_not_of(' ', found + 1); + if (found == std::string_view::npos) { + continue; + } + // We assume the value should be one-digit number. + assert_invariant(eyeCount < 10); + assert_invariant(!::isdigit(source[found + 1])); + source[found] = '0' + eyeCount; + break; + } + } } // Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and diff --git a/filament/backend/src/opengl/ShaderCompilerService.h b/filament/backend/src/opengl/ShaderCompilerService.h index bbce6a5c23f..27255ea0002 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.h +++ b/filament/backend/src/opengl/ShaderCompilerService.h @@ -21,6 +21,7 @@ #include "CallbackManager.h" #include "CompilerThreadPool.h" +#include "OpenGLBlobCache.h" #include #include @@ -94,25 +95,53 @@ class ShaderCompilerService { CallbackHandler* handler, CallbackHandler::Callback callback, void* user); private: + struct Job { + template + Job(FUNC&& fn) : fn(std::forward(fn)) {} + Job(std::function fn, + CallbackHandler* handler, void* user, CallbackHandler::Callback callback) + : fn(std::move(fn)), handler(handler), user(user), callback(callback) { + } + std::function fn; + CallbackHandler* handler = nullptr; + void* user = nullptr; + CallbackHandler::Callback callback{}; + }; + + enum class Mode { + UNDEFINED, // init() has not been called yet. + SYNCHRONOUS, // synchronous shader compilation + THREAD_POOL, // asynchronous shader compilation using a thread-pool (most common) + ASYNCHRONOUS // asynchronous shader compilation using KHR_parallel_shader_compile + }; + OpenGLDriver& mDriver; + OpenGLBlobCache mBlobCache; CallbackManager mCallbackManager; CompilerThreadPool mCompilerThreadPool; - const bool KHR_parallel_shader_compile; uint32_t mShaderCompilerThreadCount = 0u; + Mode mMode = Mode::UNDEFINED; // valid after init() is called + + using ContainerType = std::tuple; + std::vector mRunAtNextTickOps; GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept; static void getProgramFromCompilerPool(program_token_t& token) noexcept; - static void compileShaders( + static void compileShaders( OpenGLContext& context, Program::ShaderSource shadersSource, utils::FixedCapacityVector const& specializationConstants, + bool multiview, std::array& outShaders, std::array& outShaderSourceCode) noexcept; - static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, + static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, + char* source, size_t len) noexcept; + + static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source, size_t len) noexcept; static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept; @@ -125,27 +154,11 @@ class ShaderCompilerService { static bool checkProgramStatus(program_token_t const& token) noexcept; - struct Job { - template - Job(FUNC&& fn) : fn(std::forward(fn)) {} - Job(std::function fn, - CallbackHandler* handler, void* user, CallbackHandler::Callback callback) - : fn(std::move(fn)), handler(handler), user(user), callback(callback) { - } - std::function fn; - CallbackHandler* handler = nullptr; - void* user = nullptr; - CallbackHandler::Callback callback{}; - }; - void runAtNextTick(CompilerPriorityQueue priority, const program_token_t& token, Job job) noexcept; void executeTickOps() noexcept; bool cancelTickOp(program_token_t token) noexcept; // order of insertion is important - - using ContainerType = std::tuple; - std::vector mRunAtNextTickOps; }; } // namespace filament::backend diff --git a/filament/backend/src/opengl/gl_headers.cpp b/filament/backend/src/opengl/gl_headers.cpp index f35331f99e4..cb1f022d517 100644 --- a/filament/backend/src/opengl/gl_headers.cpp +++ b/filament/backend/src/opengl/gl_headers.cpp @@ -28,6 +28,7 @@ static void getProcAddress(T& pfn, const char* name) noexcept { } namespace glext { +#ifndef __EMSCRIPTEN__ #ifdef GL_OES_EGL_image PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; #endif @@ -66,16 +67,20 @@ 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; #endif - static std::once_flag sGlExtInitialized; +#endif // __EMSCRIPTEN__ void importGLESExtensionsEntryPoints() { +#ifndef __EMSCRIPTEN__ std::call_once(sGlExtInitialized, +[]() { #ifdef GL_OES_EGL_image getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES"); @@ -115,10 +120,14 @@ 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"); #endif }); +#endif // __EMSCRIPTEN__ } } // namespace glext diff --git a/filament/backend/src/opengl/gl_headers.h b/filament/backend/src/opengl/gl_headers.h index c934cb7d837..569c8ed2859 100644 --- a/filament/backend/src/opengl/gl_headers.h +++ b/filament/backend/src/opengl/gl_headers.h @@ -112,6 +112,7 @@ namespace glext { // it is currently called from PlatformEGL. void importGLESExtensionsEntryPoints(); +#ifndef __EMSCRIPTEN__ #ifdef GL_OES_EGL_image extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; #endif @@ -150,9 +151,13 @@ 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; #endif +#endif // __EMSCRIPTEN__ } // namespace glext using namespace glext; @@ -190,8 +195,10 @@ using namespace glext; #if defined(GL_EXT_clip_cull_distance) # define GL_CLIP_DISTANCE0 GL_CLIP_DISTANCE0_EXT +# define GL_CLIP_DISTANCE1 GL_CLIP_DISTANCE1_EXT #else # define GL_CLIP_DISTANCE0 0x3000 +# define GL_CLIP_DISTANCE1 0x3001 #endif #if defined(GL_KHR_debug) 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 60652b54156..b041473839f 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -18,16 +18,33 @@ #include "opengl/GLUtils.h" +#include + +#include +#include + #include #include +#include #if defined(__ANDROID__) #include #endif - #include + +#include +#include #include -#include +#include + +#include +#include +#include +#include + +#include +#include +#include #ifndef EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE # define EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE 0x3483 @@ -48,7 +65,6 @@ UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = {}; } using namespace glext; - // --------------------------------------------------------------------------------------------- // Utilities // --------------------------------------------------------------------------------------------- @@ -97,13 +113,32 @@ int PlatformEGL::getOSVersion() const noexcept { return 0; } +bool PlatformEGL::isOpenGL() const noexcept { + return false; +} Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept { mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); assert_invariant(mEGLDisplay != EGL_NO_DISPLAY); EGLint major, minor; - EGLBoolean const initialized = eglInitialize(mEGLDisplay, &major, &minor); + EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor); + + if (!initialized) { + EGLDeviceEXT eglDevice; + EGLint numDevices; + PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = + (PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT"); + if (eglQueryDevicesEXT != nullptr) { + eglQueryDevicesEXT(1, &eglDevice, &numDevices); + if(auto* getPlatformDisplay = reinterpret_cast( + eglGetProcAddress("eglGetPlatformDisplay"))) { + mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, 0); + initialized = eglInitialize(mEGLDisplay, &major, &minor); + } + } + } + if (UTILS_UNLIKELY(!initialized)) { slog.e << "eglInitialize failed" << io::endl; return nullptr; @@ -118,7 +153,8 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon ext.egl.KHR_gl_colorspace = extensions.has("EGL_KHR_gl_colorspace"); ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context"); ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context"); - ext.egl.KHR_surfaceless_context = extensions.has("KHR_surfaceless_context"); + ext.egl.KHR_surfaceless_context = extensions.has("EGL_KHR_surfaceless_context"); + ext.egl.EXT_protected_content = extensions.has("EGL_EXT_protected_content"); if (ext.egl.KHR_create_context) { // KHR_create_context implies KHR_surfaceless_context for ES3.x contexts ext.egl.KHR_surfaceless_context = true; @@ -148,10 +184,16 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon constexpr bool requestES2Context = false; #endif - // Request a ES2 context, devices that support ES3 will return an ES3 context - Config contextAttribs = { - { EGL_CONTEXT_CLIENT_VERSION, 2 }, - }; + Config contextAttribs; + + if (isOpenGL()) { + // Request a OpenGL 4.1 context + contextAttribs[EGL_CONTEXT_MAJOR_VERSION] = 4; + contextAttribs[EGL_CONTEXT_MINOR_VERSION] = 1; + } else { + // Request a ES2 context, devices that support ES3 will return an ES3 context + contextAttribs[EGL_CONTEXT_CLIENT_VERSION] = 2; + } // FOR TESTING ONLY, enforce the ES version we're asking for. // FIXME: we should check EGL_ANGLE_create_context_backwards_compatible, however, at least @@ -172,14 +214,17 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon // config use for creating the context EGLConfig eglConfig = EGL_NO_CONFIG_KHR; - // find a config we can use if we don't have "EGL_KHR_no_config_context" and that we can use - // for the dummy pbuffer surface. - mEGLConfig = findSwapChainConfig(0); - if (UTILS_UNLIKELY(mEGLConfig == EGL_NO_CONFIG_KHR)) { - goto error; // error already logged - } if (UTILS_UNLIKELY(!ext.egl.KHR_no_config_context)) { + // find a config we can use if we don't have "EGL_KHR_no_config_context" and that we can use + // for the dummy pbuffer surface. + mEGLConfig = findSwapChainConfig( + SWAP_CHAIN_CONFIG_TRANSPARENT | + SWAP_CHAIN_HAS_STENCIL_BUFFER, + true, true); + if (UTILS_UNLIKELY(mEGLConfig == EGL_NO_CONFIG_KHR)) { + goto error; // error already logged + } // if we don't have the EGL_KHR_no_config_context the context must be created with // the same config as the swapchain, so we have no choice but to create a // transparent config. @@ -238,12 +283,14 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon } } - if (UTILS_UNLIKELY(!makeCurrent(mEGLDummySurface, mEGLDummySurface))) { + if (UTILS_UNLIKELY( + egl.makeCurrent(mEGLContext, mEGLDummySurface, mEGLDummySurface) == EGL_FALSE)) { // eglMakeCurrent failed logEglError("eglMakeCurrent"); goto error; } + mCurrentContextType = ContextType::UNPROTECTED; mContextAttribs = std::move(contextAttribs); initializeGlExtensions(); @@ -262,9 +309,13 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon if (mEGLContext) { eglDestroyContext(mEGLDisplay, mEGLContext); } + if (mEGLContextProtected) { + eglDestroyContext(mEGLDisplay, mEGLContextProtected); + } mEGLDummySurface = EGL_NO_SURFACE; mEGLContext = EGL_NO_CONTEXT; + mEGLContextProtected = EGL_NO_CONTEXT; eglTerminate(mEGLDisplay); eglReleaseThread(); @@ -276,6 +327,10 @@ bool PlatformEGL::isExtraContextSupported() const noexcept { return ext.egl.KHR_surfaceless_context; } +bool PlatformEGL::isProtectedContextSupported() const noexcept { + return ext.egl.EXT_protected_content; +} + void PlatformEGL::createContext(bool shared) { EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig; @@ -310,15 +365,6 @@ void PlatformEGL::releaseContext() noexcept { eglReleaseThread(); } -EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { - if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) { - mCurrentDrawSurface = drawSurface; - mCurrentReadSurface = readSurface; - return eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext); - } - return 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); @@ -326,6 +372,9 @@ void PlatformEGL::terminate() noexcept { eglDestroySurface(mEGLDisplay, mEGLDummySurface); } eglDestroyContext(mEGLDisplay, mEGLContext); + if (mEGLContextProtected != EGL_NO_CONTEXT) { + eglDestroyContext(mEGLDisplay, mEGLContextProtected); + } for (auto context : mAdditionalContexts) { eglDestroyContext(mEGLDisplay, context); } @@ -333,22 +382,36 @@ void PlatformEGL::terminate() noexcept { eglReleaseThread(); } -EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags) const { +EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const { // Find config that support ES3. EGLConfig config = EGL_NO_CONFIG_KHR; EGLint configsCount; Config configAttribs = { - { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT }, { EGL_RED_SIZE, 8 }, { EGL_GREEN_SIZE, 8 }, { EGL_BLUE_SIZE, 8 }, { EGL_ALPHA_SIZE, (flags & SWAP_CHAIN_CONFIG_TRANSPARENT) ? 8 : 0 }, { EGL_DEPTH_SIZE, 24 }, + { EGL_STENCIL_SIZE, (flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) ? 8 : 0 } }; + if (!ext.egl.KHR_no_config_context) { + if (isOpenGL()) { + configAttribs[EGL_RENDERABLE_TYPE] = EGL_OPENGL_BIT; + } else { + configAttribs[EGL_RENDERABLE_TYPE] = EGL_OPENGL_ES2_BIT; + if (ext.egl.KHR_create_context) { + configAttribs[EGL_RENDERABLE_TYPE] |= EGL_OPENGL_ES3_BIT_KHR; + } + } + } - if (ext.egl.KHR_create_context) { - configAttribs[EGL_RECORDABLE_ANDROID] |= EGL_OPENGL_ES3_BIT_KHR; + if (window) { + configAttribs[EGL_SURFACE_TYPE] |= EGL_WINDOW_BIT; + } + + if (pbuffer) { + configAttribs[EGL_SURFACE_TYPE] |= EGL_PBUFFER_BIT; } if (ext.egl.ANDROID_recordable) { @@ -382,6 +445,8 @@ EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags) const { return config; } +// ----------------------------------------------------------------------------------------------- + bool PlatformEGL::isSRGBSwapChainSupported() const noexcept { return ext.egl.KHR_gl_colorspace; } @@ -389,53 +454,58 @@ bool PlatformEGL::isSRGBSwapChainSupported() const noexcept { Platform::SwapChain* PlatformEGL::createSwapChain( void* nativeWindow, uint64_t flags) noexcept { - EGLConfig config = EGL_NO_CONFIG_KHR; - if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) { - config = findSwapChainConfig(flags); - } 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; } + } else { + flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE; } - EGLSurface sur = eglCreateWindowSurface(mEGLDisplay, config, - (EGLNativeWindowType)nativeWindow, attribs.data()); - - if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { - logEglError("eglCreateWindowSurface"); - return nullptr; + if (ext.egl.EXT_protected_content) { + if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) { + attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE; + } + } else { + flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; } - // this is not fatal - eglSurfaceAttrib(mEGLDisplay, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED); - - return (SwapChain*)sur; -} - -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); + config = findSwapChainConfig(flags, true, false); } else { config = mEGLConfig; } - if (UTILS_UNLIKELY(config == EGL_NO_CONFIG_KHR)) { - return nullptr; + 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, + .attribs = std::move(attribs), + .nativeWindow = (EGLNativeWindowType)nativeWindow, + .config = config, + .flags = flags + }); + return sc; +} + +Platform::SwapChain* PlatformEGL::createSwapChain( + uint32_t width, uint32_t height, uint64_t flags) noexcept { + Config attribs = { { EGL_WIDTH, EGLint(width) }, { EGL_HEIGHT, EGLint(height) }, @@ -445,41 +515,149 @@ Platform::SwapChain* PlatformEGL::createSwapChain( if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) { attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR; } + } else { + flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE; + } + + if (ext.egl.EXT_protected_content) { + if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) { + attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE; + } + } else { + flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; } - EGLSurface sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data()); + 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 } - return (SwapChain*)sur; + + SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({ + .sur = sur, + .attribs = std::move(attribs), + .config = config, + .flags = flags + }); + return sc; } void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { - EGLSurface sur = (EGLSurface) swapChain; - if (sur != EGL_NO_SURFACE) { - makeCurrent(mEGLDummySurface, mEGLDummySurface); - eglDestroySurface(mEGLDisplay, sur); + if (swapChain) { + SwapChainEGL const* const sc = static_cast(swapChain); + if (sc->sur != EGL_NO_SURFACE) { + egl.makeCurrent(mEGLDummySurface, mEGLDummySurface); + eglDestroySurface(mEGLDisplay, sc->sur); + delete sc; + } } } +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); + 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) noexcept { - EGLSurface drawSur = (EGLSurface) drawSwapChain; - EGLSurface readSur = (EGLSurface) readSwapChain; - if (drawSur != EGL_NO_SURFACE || readSur != EGL_NO_SURFACE) { - makeCurrent(drawSur, readSur); + Platform::SwapChain* readSwapChain, + utils::Invocable preContextChange, + utils::Invocable postContextChange) noexcept { + + assert_invariant(drawSwapChain); + assert_invariant(readSwapChain); + + ContextType type = ContextType::UNPROTECTED; + if (ext.egl.EXT_protected_content) { + bool const swapChainProtected = isSwapChainProtected(drawSwapChain); + if (UTILS_UNLIKELY(swapChainProtected)) { + // we need a protected context + if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) { + // we don't have one, create it! + EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig; + Config protectedContextAttribs{ mContextAttribs }; + protectedContextAttribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE; + mEGLContextProtected = eglCreateContext(mEGLDisplay, config, mEGLContext, + protectedContextAttribs.data()); + if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) { + // couldn't create the protected context + logEglError("eglCreateContext[EGL_PROTECTED_CONTENT_EXT]"); + ext.egl.EXT_protected_content = false; + goto error; + } + } + type = ContextType::PROTECTED; + error: ; + } + + bool const contextChange = type != mCurrentContextType; + mCurrentContextType = type; + + if (UTILS_UNLIKELY(contextChange)) { + preContextChange(); + 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; + } + 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 = (mCurrentContextType == ContextType::PROTECTED) ? 1 : 0; + postContextChange(contextIndex); + return; + } + } + + bool const success = makeCurrent(mCurrentContextType, drawSwapChain, readSwapChain); + if (UTILS_UNLIKELY(!success)) { + logEglError("PlatformEGL::makeCurrent"); } } void PlatformEGL::commit(Platform::SwapChain* swapChain) noexcept { - EGLSurface sur = (EGLSurface) swapChain; - if (sur != EGL_NO_SURFACE) { - eglSwapBuffers(mEGLDisplay, sur); + if (swapChain) { + SwapChainEGL const* const sc = static_cast(swapChain); + if (sc->sur != EGL_NO_SURFACE) { + eglSwapBuffers(mEGLDisplay, sc->sur); + } } } +// ----------------------------------------------------------------------------------------------- + bool PlatformEGL::canCreateFence() noexcept { return true; } @@ -518,8 +696,10 @@ FenceStatus PlatformEGL::waitFence( return FenceStatus::ERROR; } +// ----------------------------------------------------------------------------------------------- + OpenGLPlatform::ExternalTexture* PlatformEGL::createExternalImageTexture() noexcept { - ExternalTexture* outTexture = new ExternalTexture{}; + ExternalTexture* outTexture = new(std::nothrow) ExternalTexture{}; glGenTextures(1, &outTexture->id); if (UTILS_LIKELY(ext.gl.OES_EGL_image_external_essl3)) { outTexture->target = GL_TEXTURE_EXTERNAL_OES; @@ -548,6 +728,8 @@ bool PlatformEGL::setExternalImage(void* externalImage, return true; } +// ----------------------------------------------------------------------------------------------- + void PlatformEGL::initializeGlExtensions() noexcept { // We're guaranteed to be on an ES platform, since we're using EGL GLUtils::unordered_string_set glExtensions; @@ -556,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; @@ -569,7 +762,7 @@ EGLint& PlatformEGL::Config::operator[](EGLint name) { auto pos = std::find_if(mConfig.begin(), mConfig.end(), [name](auto&& v) { return v.first == name; }); if (pos == mConfig.end()) { - mConfig.insert(pos - 1, { name, EGL_NONE }); + mConfig.insert(pos - 1, { name, 0 }); pos = mConfig.end() - 2; } return pos->second; @@ -592,6 +785,23 @@ void PlatformEGL::Config::erase(EGLint name) noexcept { } } -} // namespace filament::backend +// ------------------------------------------------------------------------------------------------ + +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 c273b66d624..addc5e02719 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp @@ -14,21 +14,34 @@ * limitations under the License. */ +#include +#include +#include #include #include "opengl/GLUtils.h" #include "ExternalStreamManagerAndroid.h" #include - -#include -#include +#include #include +#include #include +#include +#include + #include +#include + +#include + +#include +#include +#include + // We require filament to be built with an API 19 toolchain, before that, OpenGLES 3.0 didn't exist // Actually, OpenGL ES 3.0 was added to API 18, but API 19 is the better target and // the minimum for Jetpack at the time of this comment. @@ -129,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); } } @@ -165,14 +179,28 @@ int PlatformEGLAndroid::getOSVersion() const noexcept { AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) noexcept { // Convert the AHardwareBuffer to EGLImage. - EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID((const AHardwareBuffer*)source.image); + AHardwareBuffer const* const pHardwareBuffer = (const AHardwareBuffer*)source.image; + + EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer); if (!clientBuffer) { slog.e << "Unable to get EGLClientBuffer from AHardwareBuffer." << io::endl; return {}; } - // Note that this cannot be used to stream protected video (for now) because we do not set EGL_PROTECTED_CONTENT_EXT. - EGLint attrs[] = { EGL_NONE, EGL_NONE }; - EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); + + PlatformEGL::Config attributes; + + if (__builtin_available(android 26, *)) { + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(pHardwareBuffer, &desc); + bool const isProtectedContent = + desc.usage & AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT; + if (isProtectedContent) { + attributes[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE; + } + } + + EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay, + EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attributes.data()); if (eglImage == EGL_NO_IMAGE_KHR) { slog.e << "eglCreateImageKHR returned no image." << io::endl; return {}; @@ -185,7 +213,7 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) n AcquiredImage acquiredImage; EGLDisplay display; }; - Closure* closure = new Closure(source, mEGLDisplay); + Closure* closure = new(std::nothrow) Closure(source, mEGLDisplay); auto patchedCallback = [](void* image, void* userdata) { Closure* closure = (Closure*)userdata; if (eglDestroyImageKHR(closure->display, (EGLImageKHR) image) == EGL_FALSE) { diff --git a/filament/backend/src/opengl/platforms/PlatformEGLHeadless.cpp b/filament/backend/src/opengl/platforms/PlatformEGLHeadless.cpp index befebaa62da..b3c53716bd4 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLHeadless.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLHeadless.cpp @@ -30,21 +30,14 @@ using namespace utils; namespace filament { using namespace backend; -namespace glext { -UTILS_PRIVATE PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = {}; -UTILS_PRIVATE PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = {}; -UTILS_PRIVATE PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR = {}; -UTILS_PRIVATE PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = {}; -UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = {}; -} -using namespace glext; - -// --------------------------------------------------------------------------------------------- - PlatformEGLHeadless::PlatformEGLHeadless() noexcept : PlatformEGL() { } +bool PlatformEGLHeadless::isOpenGL() const noexcept { + return true; +} + backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept { EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API); @@ -58,166 +51,7 @@ backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext, return nullptr; } - // Copied from the base class and modified slightly. Should be cleaned up/improved later. - mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - assert_invariant(mEGLDisplay != EGL_NO_DISPLAY); - - EGLint major, minor; - EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor); - - if (!initialized) { - EGLDeviceEXT eglDevice; - EGLint numDevices; - - PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = - (PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT"); - if (eglQueryDevicesEXT != NULL) { - eglQueryDevicesEXT(1, &eglDevice, &numDevices); - if(auto* getPlatformDisplay = reinterpret_cast( - eglGetProcAddress("eglGetPlatformDisplay"))) { - mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, 0); - initialized = eglInitialize(mEGLDisplay, &major, &minor); - } - } - } - - if (UTILS_UNLIKELY(!initialized)) { - slog.e << "eglInitialize failed" << io::endl; - return nullptr; - } - - auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS)); - - eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR"); - eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR"); - eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC) eglGetProcAddress("eglClientWaitSyncKHR"); - - eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR"); - eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR"); - - EGLint configsCount; - - EGLint configAttribs[] = { - EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 0, - EGL_DEPTH_SIZE, 32, - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - EGL_NONE - }; - - EGLint contextAttribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 3, - EGL_NONE, EGL_NONE, // reserved for EGL_CONTEXT_OPENGL_NO_ERROR_KHR below - EGL_NONE - }; - - EGLint pbufferAttribs[] = { - EGL_WIDTH, 1, - EGL_HEIGHT, 1, - EGL_NONE - }; - -#ifdef NDEBUG - // When we don't have a shared context and we're in release mode, we always activate the - // EGL_KHR_create_context_no_error extension. - if (!sharedContext && extensions.has("EGL_KHR_create_context_no_error")) { - contextAttribs[2] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; - contextAttribs[3] = EGL_TRUE; - } -#endif - - EGLConfig eglConfig = nullptr; - - // find an opaque config - if (!eglChooseConfig(mEGLDisplay, configAttribs, &mEGLConfig, 1, &configsCount)) { - logEglError("eglChooseConfig"); - goto error; - } - - // fallback to a 24-bit depth buffer - if (configsCount == 0) { - configAttribs[10] = EGL_DEPTH_SIZE; - configAttribs[11] = 24; - - if (!eglChooseConfig(mEGLDisplay, configAttribs, &mEGLConfig, 1, &configsCount)) { - logEglError("eglChooseConfig"); - goto error; - } - } - - // find a transparent config - configAttribs[8] = EGL_ALPHA_SIZE; - configAttribs[9] = 8; - if (!eglChooseConfig(mEGLDisplay, configAttribs, &mEGLTransparentConfig, 1, &configsCount) || - (configAttribs[13] == EGL_DONT_CARE && configsCount == 0)) { - logEglError("eglChooseConfig"); - goto error; - } - - if (!extensions.has("EGL_KHR_no_config_context")) { - // if we have the EGL_KHR_no_config_context, we don't need to worry about the config - // when creating the context, otherwise, we must always pick a transparent config. - eglConfig = mEGLConfig = mEGLTransparentConfig; - } - - // the pbuffer dummy surface is always created with a transparent surface because - // either we have EGL_KHR_no_config_context and it doesn't matter, or we don't and - // we must use a transparent surface - mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLTransparentConfig, pbufferAttribs); - if (mEGLDummySurface == EGL_NO_SURFACE) { - logEglError("eglCreatePbufferSurface"); - goto error; - } - - mEGLContext = eglCreateContext(mEGLDisplay, eglConfig, (EGLContext)sharedContext, contextAttribs); - if (mEGLContext == EGL_NO_CONTEXT && sharedContext && - extensions.has("EGL_KHR_create_context_no_error")) { - // context creation could fail because of EGL_CONTEXT_OPENGL_NO_ERROR_KHR - // not matching the sharedContext. Try with it. - contextAttribs[2] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; - contextAttribs[3] = EGL_TRUE; - mEGLContext = eglCreateContext(mEGLDisplay, eglConfig, (EGLContext)sharedContext, contextAttribs); - } - if (UTILS_UNLIKELY(mEGLContext == EGL_NO_CONTEXT)) { - // eglCreateContext failed - logEglError("eglCreateContext"); - goto error; - } - - if (!makeCurrent(mEGLDummySurface, mEGLDummySurface)) { - // eglMakeCurrent failed - logEglError("eglMakeCurrent"); - goto error; - } - - initializeGlExtensions(); - - clearGlError(); - - // success!! - return OpenGLPlatform::createDefaultDriver(this, sharedContext, driverConfig); - -error: - // if we're here, we've failed - if (mEGLDummySurface) { - eglDestroySurface(mEGLDisplay, mEGLDummySurface); - } - if (mEGLContext) { - eglDestroyContext(mEGLDisplay, mEGLContext); - } - - mEGLDummySurface = EGL_NO_SURFACE; - mEGLContext = EGL_NO_CONTEXT; - - eglTerminate(mEGLDisplay); - eglReleaseThread(); - - return nullptr; + return PlatformEGL::createDriver(sharedContext, driverConfig); } } // namespace filament - -// --------------------------------------------------------------------------------------------- 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 f2943ee0911..26746b708e6 100644 --- a/filament/backend/src/opengl/platforms/PlatformWGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformWGL.cpp @@ -168,7 +168,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext, } bool PlatformWGL::isExtraContextSupported() const noexcept { - return true; + return false; } void PlatformWGL::createContext(bool shared) { @@ -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/ostream.cpp b/filament/backend/src/ostream.cpp index c3f97dc8fdd..36d93dfbec3 100644 --- a/filament/backend/src/ostream.cpp +++ b/filament/backend/src/ostream.cpp @@ -293,20 +293,6 @@ io::ostream& operator<<(io::ostream& out, TextureFormat format) { return out; } -io::ostream& operator<<(io::ostream& out, TextureUsage usage) { - switch (usage) { - CASE(TextureUsage, NONE) - CASE(TextureUsage, DEFAULT) - CASE(TextureUsage, COLOR_ATTACHMENT) - CASE(TextureUsage, DEPTH_ATTACHMENT) - CASE(TextureUsage, STENCIL_ATTACHMENT) - CASE(TextureUsage, UPLOADABLE) - CASE(TextureUsage, SAMPLEABLE) - CASE(TextureUsage, SUBPASS_INPUT) - } - return out; -} - io::ostream& operator<<(io::ostream& out, TextureCubemapFace face) { switch (face) { CASE(TextureCubemapFace, NEGATIVE_X) @@ -469,7 +455,7 @@ io::ostream& operator<<(io::ostream& out, filament::backend::Viewport const& vie io::ostream& operator<<(io::ostream& out, TargetBufferFlags flags) { // TODO: implement decoding of enum - out << uint8_t(flags); + out << uint32_t(flags); return out; } diff --git a/filament/backend/src/vulkan/VulkanBlitter.cpp b/filament/backend/src/vulkan/VulkanBlitter.cpp index bfda73fa326..c4316cf1ec8 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.cpp +++ b/filament/backend/src/vulkan/VulkanBlitter.cpp @@ -38,22 +38,15 @@ using ImgUtil = VulkanImageUtility; namespace { inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter, - const VkExtent2D srcExtent, VulkanAttachment src, VulkanAttachment dst, + VulkanAttachment src, VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) { - const VkImageBlit blitRegions[1] = {{ - .srcSubresource = {aspect, src.level, src.layer, 1}, - .srcOffsets = {srcRect[0], srcRect[1]}, - .dstSubresource = {aspect, dst.level, dst.layer, 1}, - .dstOffsets = {dstRect[0], dstRect[1]}, - }}; - const VkImageResolve resolveRegions[1] = {{ - .srcSubresource = {aspect, src.level, src.layer, 1}, - .srcOffset = srcRect[0], - .dstSubresource = {aspect, dst.level, dst.layer, 1}, - .dstOffset = dstRect[0], - .extent = {srcExtent.width, srcExtent.height, 1}, - }}; + if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) { + utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level + << " layout=" << src.getLayout() + << " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level + << " layout=" << dst.getLayout() << utils::io::endl; + } const VkImageSubresourceRange srcRange = { .aspectMask = aspect, @@ -71,6 +64,36 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, .layerCount = 1, }; + VulkanLayout oldSrcLayout = src.getLayout(); + VulkanLayout oldDstLayout = dst.getLayout(); + + src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC); + dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST); + + const VkImageBlit blitRegions[1] = {{ + .srcSubresource = { aspect, src.level, src.layer, 1 }, + .srcOffsets = { srcRect[0], srcRect[1] }, + .dstSubresource = { aspect, dst.level, dst.layer, 1 }, + .dstOffsets = { dstRect[0], dstRect[1] }, + }}; + vkCmdBlitImage(cmdbuffer, + src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC), + dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST), + 1, blitRegions, filter); + + if (oldSrcLayout == VulkanLayout::UNDEFINED) { + oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage); + } + if (oldDstLayout == VulkanLayout::UNDEFINED) { + oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage); + } + src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout); + dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout); +} + +inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, + VulkanAttachment src, VulkanAttachment dst) { + if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) { utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level << " layout=" << src.getLayout() @@ -78,25 +101,41 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, << " layout=" << dst.getLayout() << utils::io::endl; } + const VkImageSubresourceRange srcRange = { + .aspectMask = aspect, + .baseMipLevel = src.level, + .levelCount = 1, + .baseArrayLayer = src.layer, + .layerCount = 1, + }; + + const VkImageSubresourceRange dstRange = { + .aspectMask = aspect, + .baseMipLevel = dst.level, + .levelCount = 1, + .baseArrayLayer = dst.layer, + .layerCount = 1, + }; + VulkanLayout oldSrcLayout = src.getLayout(); VulkanLayout oldDstLayout = dst.getLayout(); src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC); dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST); - if (src.texture->samples > 1 && dst.texture->samples == 1) { - assert_invariant( - aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported."); - vkCmdResolveImage(cmdbuffer, - src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC), - dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST), - 1, resolveRegions); - } else { - vkCmdBlitImage(cmdbuffer, - src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC), - dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST), - 1, blitRegions, filter); - } + assert_invariant( + aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported."); + const VkImageResolve resolveRegions[1] = {{ + .srcSubresource = { aspect, src.level, src.layer, 1 }, + .srcOffset = { 0, 0 }, + .dstSubresource = { aspect, dst.level, dst.layer, 1 }, + .dstOffset = { 0, 0 }, + .extent = { src.getExtent2D().width, src.getExtent2D().height, 1 }, + }}; + vkCmdResolveImage(cmdbuffer, + src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC), + dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST), + 1, resolveRegions); if (oldSrcLayout == VulkanLayout::UNDEFINED) { oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage); @@ -115,53 +154,42 @@ struct BlitterUniforms { }// anonymous namespace -VulkanBlitter::VulkanBlitter(VulkanStagePool& stagePool, VulkanPipelineCache& pipelineCache, - VulkanFboCache& fboCache, VulkanSamplerCache& samplerCache) noexcept - : mStagePool(stagePool), mPipelineCache(pipelineCache), mFramebufferCache(fboCache), - mSamplerCache(samplerCache) {} - -void VulkanBlitter::initialize(VkPhysicalDevice physicalDevice, VkDevice device, - VmaAllocator allocator, VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept { - mPhysicalDevice = physicalDevice; - mDevice = device; - mAllocator = allocator; - mCommands = commands; - mEmptyTexture = emptyTexture; -} +VulkanBlitter::VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept + : mPhysicalDevice(physicalDevice), + mCommands(commands) {} + +void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) { + + // src and dst should have the same aspect here + VkImageAspectFlags const aspect = src.texture->getImageAspect(); -void VulkanBlitter::blitColor(BlitArgs args) { - const VulkanAttachment src = args.srcTarget->getColor(args.targetIndex); - const VulkanAttachment dst = args.dstTarget->getColor(0); - const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT; + assert_invariant(!(aspect & VK_IMAGE_ASPECT_DEPTH_BIT)); #if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT) VkPhysicalDevice const gpu = mPhysicalDevice; VkFormatProperties info; vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info); if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT, - "Source format is not blittable %d", src.getFormat())) { + "Depth src format is not blittable %d", src.getFormat())) { return; } vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info); if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT, - "Destination format is not blittable %d", dst.getFormat())) { + "Depth dst format is not blittable %d", dst.getFormat())) { return; } #endif + VulkanCommandBuffer& commands = mCommands->get(); VkCommandBuffer const cmdbuffer = commands.buffer(); commands.acquire(src.texture); commands.acquire(dst.texture); - - blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, - args.srcRectPair, args.dstRectPair); + resolveFast(cmdbuffer, aspect, src, dst); } -void VulkanBlitter::blitDepth(BlitArgs args) { - const VulkanAttachment src = args.srcTarget->getDepth(); - const VulkanAttachment dst = args.dstTarget->getDepth(); - const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT; - +void VulkanBlitter::blit(VkFilter filter, + VulkanAttachment dst, const VkOffset3D* dstRectPair, + VulkanAttachment src, const VkOffset3D* srcRectPair) { #if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT) VkPhysicalDevice const gpu = mPhysicalDevice; VkFormatProperties info; @@ -176,287 +204,16 @@ void VulkanBlitter::blitDepth(BlitArgs args) { return; } #endif - - assert_invariant(src.texture && dst.texture); - - if (src.texture->samples > 1 && dst.texture->samples == 1) { - blitSlowDepth(args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair, - args.dstRectPair); - return; - } - + // src and dst should have the same aspect here + VkImageAspectFlags const aspect = src.texture->getImageAspect(); VulkanCommandBuffer& commands = mCommands->get(); VkCommandBuffer const cmdbuffer = commands.buffer(); commands.acquire(src.texture); commands.acquire(dst.texture); - blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair, - args.dstRectPair); + blitFast(cmdbuffer, aspect, filter, src, dst, srcRectPair, dstRectPair); } void VulkanBlitter::terminate() noexcept { - if (mDepthResolveProgram) { - delete mDepthResolveProgram; - mDepthResolveProgram = nullptr; - } - - if (mTriangleBuffer) { - delete mTriangleBuffer; - mTriangleBuffer = nullptr; - } - - if (mParamsBuffer) { - delete mParamsBuffer; - mParamsBuffer = nullptr; - } -} - -// If we created these shader modules in the constructor, the device might not be ready yet. -// It is easier to do lazy initialization, which can also improve load time. -void VulkanBlitter::lazyInit() noexcept { - if (mDepthResolveProgram != nullptr) { - return; - } - assert_invariant(mDevice); - - auto decode = [device = mDevice](const uint8_t* compressed, int compressedSize) { - const size_t spirvSize = smolv::GetDecodedBufferSize(compressed, compressedSize); - FixedCapacityVector spirv(spirvSize); - smolv::Decode(compressed, compressedSize, spirv.data(), spirvSize); - - VkShaderModuleCreateInfo moduleInfo = {}; - moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - moduleInfo.codeSize = spirvSize; - moduleInfo.pCode = (uint32_t*) spirv.data(); - - VkShaderModule result = VK_NULL_HANDLE; - vkCreateShaderModule(device, &moduleInfo, VKALLOC, &result); - assert_invariant(result); - - return result; - }; - - VkShaderModule vertexShader = decode(VKSHADERS_BLITDEPTHVS_DATA, VKSHADERS_BLITDEPTHVS_SIZE); - VkShaderModule fragmentShader = decode(VKSHADERS_BLITDEPTHFS_DATA, VKSHADERS_BLITDEPTHFS_SIZE); - - // Allocate one anonymous sampler at slot 0. - VulkanProgram::CustomSamplerInfoList samplers = { - {0, 0, ShaderStageFlags::FRAGMENT}, - }; - mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader, samplers); - -#if FVK_ENABLED(FVK_DEBUG_BLITTER) - utils::slog.d << "Created Shader Module for VulkanBlitter " - << "shaders = (" << vertexShader << ", " << fragmentShader << ")" - << utils::io::endl; -#endif - - static const float kTriangleVertices[] = { - -1.0f, -1.0f, - +1.0f, -1.0f, - -1.0f, +1.0f, - +1.0f, +1.0f, - }; - - VulkanCommandBuffer& commands = mCommands->get(); - VkCommandBuffer const cmdbuffer = commands.buffer(); - - mTriangleBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - sizeof(kTriangleVertices)); - - mTriangleBuffer->loadFromCpu(cmdbuffer, kTriangleVertices, 0, sizeof(kTriangleVertices)); - - mParamsBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - sizeof(BlitterUniforms)); -} - -// At a high level, the procedure for resolving depth looks like this: -// 1. Load uniforms -// 2. Begin render pass -// 3. Draw a big triangle -// 4. End render pass. -void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src, - VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) { - lazyInit(); - - VulkanCommandBuffer* commands = &mCommands->get(); - VkCommandBuffer const cmdbuffer = commands->buffer(); - commands->acquire(src.texture); - commands->acquire(dst.texture); - - BlitterUniforms const uniforms = { - .sampleCount = src.texture->samples, - .inverseSampleCount = 1.0f / float(src.texture->samples), - }; - mParamsBuffer->loadFromCpu(cmdbuffer, &uniforms, 0, sizeof(uniforms)); - - VkImageAspectFlags const aspect = VK_IMAGE_ASPECT_DEPTH_BIT; - - // We will transition the src into sampler layout and also keep it in sampler layout for - // consistency. - VulkanLayout const samplerLayout = VulkanLayout::DEPTH_SAMPLER; - - // BEGIN RENDER PASS - // ----------------- - - const VulkanFboCache::RenderPassKey rpkey = { - .initialColorLayoutMask = 0, - .initialDepthLayout = VulkanLayout::UNDEFINED, - .renderPassDepthLayout = samplerLayout, - .finalDepthLayout = samplerLayout, - .depthFormat = dst.getFormat(), - .clear = {}, - .discardStart = TargetBufferFlags::DEPTH, - .samples = 1, - }; - - const VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey); - mPipelineCache.bindRenderPass(renderPass, 0); - - const VulkanFboCache::FboKey fbkey { - .renderPass = renderPass, - .width = (uint16_t) (dst.texture->width >> dst.level), - .height = (uint16_t) (dst.texture->height >> dst.level), - .layers = 1, - .samples = rpkey.samples, - .color = {}, - .resolve = {}, - .depth = dst.getImageView(aspect), - }; - const VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey); - - VkRenderPassBeginInfo renderPassInfo { - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - .renderPass = renderPass, - .framebuffer = vkfb, - }; - renderPassInfo.renderArea.offset.x = dstRect[0].x; - renderPassInfo.renderArea.offset.y = dstRect[0].y; - renderPassInfo.renderArea.extent.width = dstRect[1].x - dstRect[0].x; - renderPassInfo.renderArea.extent.height = dstRect[1].y - dstRect[0].y; - - // We need to transition the source into a sampler since it'll be sampled in the shader. - const VkImageSubresourceRange srcRange = { - .aspectMask = aspect, - .baseMipLevel = src.level, - .levelCount = 1, - .baseArrayLayer = src.layer, - .layerCount = 1, - }; - src.texture->transitionLayout(cmdbuffer, srcRange, samplerLayout); - - vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - - VkViewport viewport = { - .x = (float) dstRect[0].x, - .y = (float) dstRect[0].y, - .width = (float) renderPassInfo.renderArea.extent.width, - .height = (float) renderPassInfo.renderArea.extent.height, - .minDepth = 0.0f, - .maxDepth = 1.0f - }; - - vkCmdSetViewport(cmdbuffer, 0, 1, &viewport); - - // DRAW THE TRIANGLE - // ----------------- - - mPipelineCache.bindProgram(mDepthResolveProgram); - mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); - - auto vkraster = mPipelineCache.getCurrentRasterState(); - vkraster.depthWriteEnable = true; - vkraster.depthCompareOp = SamplerCompareFunc::A; - vkraster.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, - vkraster.alphaToCoverageEnable = false, - vkraster.blendEnable = VK_FALSE, - vkraster.srcColorBlendFactor = VK_BLEND_FACTOR_ONE, - vkraster.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, - vkraster.colorBlendOp = BlendEquation::ADD, - vkraster.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, - vkraster.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, - vkraster.alphaBlendOp = BlendEquation::ADD, - vkraster.colorWriteMask = (VkColorComponentFlags) 0, - vkraster.cullMode = VK_CULL_MODE_NONE; - vkraster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - vkraster.depthBiasEnable = VK_FALSE; - vkraster.colorTargetCount = 0; - mPipelineCache.bindRasterState(vkraster); - - VkBuffer buffers[1] = {}; - VkDeviceSize offsets[1] = {}; - buffers[0] = mTriangleBuffer->getGpuBuffer(); - VkVertexInputAttributeDescription attribDesc = { - .location = 0, - .binding = 0, - .format = VK_FORMAT_R32G32_SFLOAT, - }; - VkVertexInputBindingDescription bufferDesc = { - .binding = 0, - .stride = sizeof(float) * 2, - }; - mPipelineCache.bindVertexArray(&attribDesc, &bufferDesc, 1); - - // Select nearest filtering and clamp_to_edge. - VkSampler vksampler = mSamplerCache.getSampler({}); - - VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT]; - VulkanTexture* textures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr}; - for (auto& sampler : samplers) { - sampler = { - .sampler = vksampler, - .imageView = mEmptyTexture->getPrimaryImageView(), - .imageLayout = ImgUtil::getVkLayout(VulkanLayout::READ_WRITE), - }; - } - - samplers[0] = { - .sampler = vksampler, - .imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT), - .imageLayout = ImgUtil::getVkLayout(samplerLayout), - }; - textures[0] = src.texture; - - mPipelineCache.bindSamplers(samplers, textures, - VulkanPipelineCache::getUsageFlags(0, ShaderStageFlags::FRAGMENT)); - - auto previousUbo = mPipelineCache.getUniformBufferBinding(0); - mPipelineCache.bindUniformBuffer(0, mParamsBuffer->getGpuBuffer()); - - if (!mPipelineCache.bindDescriptors(cmdbuffer)) { - assert_invariant(false); - } - - const VkRect2D scissor = { - .offset = renderPassInfo.renderArea.offset, - .extent = renderPassInfo.renderArea.extent, - }; - - mPipelineCache.bindScissor(cmdbuffer, scissor); - - if (!mPipelineCache.bindPipeline(commands)) { - assert_invariant(false); - } - - vkCmdBindVertexBuffers(cmdbuffer, 0, 1, buffers, offsets); - vkCmdDraw(cmdbuffer, 4, 1, 0, 0); - - // END RENDER PASS - // --------------- - - vkCmdEndRenderPass(cmdbuffer); - - VkMemoryBarrier barrier { - .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - }; - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - vkCmdPipelineBarrier(cmdbuffer, srcStageMask, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | // <== For Mali - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, 1, &barrier, 0, nullptr, 0, nullptr); - - mPipelineCache.bindUniformBuffer(0, previousUbo.buffer, previousUbo.offset, previousUbo.size); } } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanBlitter.h b/filament/backend/src/vulkan/VulkanBlitter.h index 2a53d286f55..41f2516f1dd 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.h +++ b/filament/backend/src/vulkan/VulkanBlitter.h @@ -17,6 +17,7 @@ #ifndef TNT_FILAMENT_BACKEND_VULKANBLITTER_H #define TNT_FILAMENT_BACKEND_VULKANBLITTER_H +#include "VulkanCommands.h" #include "VulkanContext.h" #include @@ -32,46 +33,19 @@ struct VulkanProgram; class VulkanBlitter { public: - VulkanBlitter(VulkanStagePool& stagePool, VulkanPipelineCache& pipelineCache, - VulkanFboCache& fboCache, VulkanSamplerCache& samplerCache) noexcept; + VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept; - void initialize(VkPhysicalDevice physicalDevice, VkDevice device, VmaAllocator allocator, - VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept; + void blit(VkFilter filter, + VulkanAttachment dst, const VkOffset3D* dstRectPair, + VulkanAttachment src, const VkOffset3D* srcRectPair); - struct BlitArgs { - const VulkanRenderTarget* dstTarget; - const VkOffset3D* dstRectPair; - const VulkanRenderTarget* srcTarget; - const VkOffset3D* srcRectPair; - VkFilter filter = VK_FILTER_NEAREST; - int targetIndex = 0; - }; - - void blitColor(BlitArgs args); - void blitDepth(BlitArgs args); + void resolve(VulkanAttachment dst, VulkanAttachment src); void terminate() noexcept; private: - void lazyInit() noexcept; - - void blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src, - VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]); - - VulkanBuffer* mTriangleBuffer = nullptr; - VulkanBuffer* mParamsBuffer = nullptr; - VulkanProgram* mDepthResolveProgram = nullptr; - UTILS_UNUSED VkPhysicalDevice mPhysicalDevice; - VkDevice mDevice; - VmaAllocator mAllocator; VulkanCommands* mCommands; - VulkanTexture* mEmptyTexture; - - VulkanStagePool& mStagePool; - VulkanPipelineCache& mPipelineCache; - VulkanFboCache& mFramebufferCache; - VulkanSamplerCache& mSamplerCache; }; } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanBuffer.h b/filament/backend/src/vulkan/VulkanBuffer.h index d9a87962cef..db24edf3943 100644 --- a/filament/backend/src/vulkan/VulkanBuffer.h +++ b/filament/backend/src/vulkan/VulkanBuffer.h @@ -19,6 +19,7 @@ #include "VulkanContext.h" #include "VulkanStagePool.h" +#include "VulkanMemory.h" namespace filament::backend { diff --git a/filament/backend/src/vulkan/VulkanCommands.cpp b/filament/backend/src/vulkan/VulkanCommands.cpp index 39674fc0b44..95c31657e13 100644 --- a/filament/backend/src/vulkan/VulkanCommands.cpp +++ b/filament/backend/src/vulkan/VulkanCommands.cpp @@ -47,7 +47,8 @@ VulkanCmdFence::VulkanCmdFence(VkFence ifence) VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, VkCommandPool pool) - : mResourceManager(allocator) { + : mResourceManager(allocator), + mPipeline(VK_NULL_HANDLE) { // Create the low-level command buffer. const VkCommandBufferAllocateInfo allocateInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, @@ -155,7 +156,7 @@ VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFam #endif } -VulkanCommands::~VulkanCommands() { +void VulkanCommands::terminate() { wait(); gc(); vkDestroyCommandPool(mDevice, mPool, VKALLOC); @@ -268,13 +269,18 @@ bool VulkanCommands::flush() { VK_NULL_HANDLE, VK_NULL_HANDLE, }; - + uint32_t waitSemaphoreCount = 0; + if (mSubmissionSignal) { + signals[waitSemaphoreCount++] = mSubmissionSignal; + } + if (mInjectedSignal) { + signals[waitSemaphoreCount++] = mInjectedSignal; + } VkCommandBuffer const cmdbuffer = currentbuf->buffer(); - VkSubmitInfo submitInfo{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .waitSemaphoreCount = 0, - .pWaitSemaphores = signals, + .waitSemaphoreCount = waitSemaphoreCount, + .pWaitSemaphores = waitSemaphoreCount > 0 ? signals : nullptr, .pWaitDstStageMask = waitDestStageMasks, .commandBufferCount = 1, .pCommandBuffers = &cmdbuffer, @@ -282,19 +288,6 @@ bool VulkanCommands::flush() { .pSignalSemaphores = &renderingFinished, }; - if (mSubmissionSignal) { - signals[submitInfo.waitSemaphoreCount++] = mSubmissionSignal; - } - - if (mInjectedSignal) { - signals[submitInfo.waitSemaphoreCount++] = mInjectedSignal; - } - - // To address a validation warning. - if (submitInfo.waitSemaphoreCount == 0) { - submitInfo.pWaitSemaphores = VK_NULL_HANDLE; - } - #if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) slog.i << "Submitting cmdbuffer=" << cmdbuffer << " wait=(" << signals[0] << ", " << signals[1] << ") " @@ -443,8 +436,8 @@ void VulkanCommands::popGroupMarker() { auto const [marker, startTime] = mGroupMarkers->pop(); auto const endTime = std::chrono::high_resolution_clock::now(); std::chrono::duration diff = endTime - startTime; - utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms\n" - << utils::io::flush; + utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms" + << utils::io::endl; #else mGroupMarkers->pop(); #endif diff --git a/filament/backend/src/vulkan/VulkanCommands.h b/filament/backend/src/vulkan/VulkanCommands.h index 29de6f772fc..e3c7a92f190 100644 --- a/filament/backend/src/vulkan/VulkanCommands.h +++ b/filament/backend/src/vulkan/VulkanCommands.h @@ -89,6 +89,15 @@ struct VulkanCommandBuffer { inline void reset() { fence.reset(); mResourceManager.clear(); + mPipeline = VK_NULL_HANDLE; + } + + inline void setPipeline(VkPipeline pipeline) { + mPipeline = pipeline; + } + + inline VkPipeline pipeline() const { + return mPipeline; } inline VkCommandBuffer buffer() const { @@ -103,6 +112,7 @@ struct VulkanCommandBuffer { private: VulkanAcquireOnlyResourceManager mResourceManager; VkCommandBuffer mBuffer; + VkPipeline mPipeline; }; // Allows classes to be notified after a new command buffer has been activated. @@ -139,71 +149,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/VulkanConstants.h b/filament/backend/src/vulkan/VulkanConstants.h index 429060535d3..b4974950ef5 100644 --- a/filament/backend/src/vulkan/VulkanConstants.h +++ b/filament/backend/src/vulkan/VulkanConstants.h @@ -39,8 +39,16 @@ // not required for validation. // FVK is short for Filament Vulkan + +// Enables Android systrace #define FVK_DEBUG_SYSTRACE 0x00000001 -#define FVK_DEBUG_GROUP_MARKERS 0x00000002 + +// Group markers are used to denote collections of GPU commands. It is typically at the +// granualarity of a renderpass. You can enable this along with FVK_DEBUG_DEBUG_UTILS to take +// advantage of vkCmdBegin/EndDebugUtilsLabelEXT. You can also just enable this with +// FVK_DEBUG_PRINT_GROUP_MARKERS to print the current marker to stdout. +#define FVK_DEBUG_GROUP_MARKERS 0x00000002 + #define FVK_DEBUG_TEXTURE 0x00000004 #define FVK_DEBUG_LAYOUT_TRANSITION 0x00000008 #define FVK_DEBUG_COMMAND_BUFFER 0x00000010 @@ -55,11 +63,17 @@ #define FVK_DEBUG_PIPELINE_CACHE 0x00002000 #define FVK_DEBUG_ALLOCATION 0x00004000 -// Usefaul default combinations +// Enable the debug utils extension if it is available. +#define FVK_DEBUG_DEBUG_UTILS 0x00008000 + +// Use this to debug potential Handle/Resource leakage. It will print out reference counts for all +// the currently active resources. +#define FVK_DEBUG_RESOURCE_LEAK 0x00010000 + +// Useful default combinations #define FVK_DEBUG_EVERYTHING 0xFFFFFFFF #define FVK_DEBUG_PERFORMANCE \ - FVK_DEBUG_SYSTRACE | \ - FVK_DEBUG_GROUP_MARKERS + FVK_DEBUG_SYSTRACE #define FVK_DEBUG_CORRECTNESS \ FVK_DEBUG_VALIDATION | \ @@ -72,21 +86,40 @@ FVK_DEBUG_PRINT_GROUP_MARKERS #ifndef NDEBUG -#define FVK_DEBUG_FLAGS FVK_DEBUG_PERFORMANCE +#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE) #else #define FVK_DEBUG_FLAGS 0 #endif -#define FVK_ENABLED(flags) ((FVK_DEBUG_FLAGS) & (flags)) -#define FVK_ENABLED_BOOL(flags) ((bool) FVK_ENABLED(flags)) +#define FVK_ENABLED(flags) (((FVK_DEBUG_FLAGS) & (flags)) == (flags)) + +// Group marker only works only if validation or debug utils is enabled since it uses +// vkCmd(Begin/End)DebugUtilsLabelEXT or vkCmdDebugMarker(Begin/End)EXT +#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) +static_assert(FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_VALIDATION)); +#endif // Ensure dependencies are met between debug options #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) static_assert(FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)); #endif +// Only enable debug utils if validation is enabled. +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) +static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION)); +#endif + // end dependcy checks +// Shorthand for combination of enabled debug flags +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_TEXTURE) +#define FVK_ENABLED_DEBUG_SAMPLER_NAME 1 +#else +#define FVK_ENABLED_DEBUG_SAMPLER_NAME 0 +#endif + +// end shorthands + #if FVK_ENABLED(FVK_DEBUG_SYSTRACE) #include diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index c9a4fa2c4e7..7c60f576b35 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -19,7 +19,6 @@ #include "VulkanConstants.h" #include "VulkanImageUtility.h" -#include "VulkanPipelineCache.h" #include "VulkanUtility.h" #include @@ -42,7 +41,7 @@ struct VulkanTimerQuery; struct VulkanCommandBuffer; struct VulkanAttachment { - VulkanTexture* texture; + VulkanTexture* texture = nullptr; uint8_t level = 0; uint16_t layer = 0; VkImage getImage() const; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index f82ea485839..e1cd3d7cf74 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "vulkan/VulkanDriver.h" +#include "VulkanDriver.h" #include "CommandStreamDispatcher.h" #include "DataReshaper.h" @@ -24,6 +24,7 @@ #include "VulkanHandles.h" #include "VulkanImageUtility.h" #include "VulkanMemory.h" +#include "VulkanTexture.h" #include @@ -54,7 +55,8 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic VmaAllocator allocator; VmaVulkanFunctions const funcs { #if VMA_DYNAMIC_VULKAN_FUNCTIONS - .vkGetInstanceProcAddr = vkGetInstanceProcAddr, .vkGetDeviceProcAddr = vkGetDeviceProcAddr, + .vkGetInstanceProcAddr = vkGetInstanceProcAddr, + .vkGetDeviceProcAddr = vkGetDeviceProcAddr, #else .vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties, .vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties, @@ -100,6 +102,15 @@ VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevi return emptyTexture; } +VulkanBufferObject* createEmptyBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, + VulkanCommands* commands) { + VulkanBufferObject* obj = + new VulkanBufferObject(allocator, stagePool, 1, BufferObjectBinding::UNIFORM); + uint8_t byte = 0; + obj->buffer.loadFromCpu(commands->get().buffer(), &byte, 0, 1); + return obj; +} + #if FVK_ENABLED(FVK_DEBUG_VALIDATION) VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, @@ -116,7 +127,9 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags, utils::slog.e << utils::io::endl; return VK_FALSE; } +#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION) +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT types, const VkDebugUtilsMessengerCallbackDataEXT* cbdata, void* pUserData) { @@ -135,9 +148,63 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla utils::slog.e << utils::io::endl; return VK_FALSE; } +#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS) + +}// anonymous namespace + +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) +using DebugUtils = VulkanDriver::DebugUtils; +DebugUtils* DebugUtils::mSingleton = nullptr; + +DebugUtils::DebugUtils(VkInstance instance, VkDevice device, VulkanContext const* context) + : mInstance(instance), mDevice(device), mEnabled(context->isDebugUtilsSupported()) { + +#if FVK_ENABLED(FVK_DEBUG_VALIDATION) + // Also initialize debug utils messenger here + if (mEnabled) { + VkDebugUtilsMessengerCreateInfoEXT const createInfo = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + .pfnUserCallback = debugUtilsCallback, + }; + VkResult result = vkCreateDebugUtilsMessengerEXT(instance, &createInfo, + VKALLOC, &mDebugMessenger); + ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug messenger."); + } #endif // FVK_EANBLED(FVK_DEBUG_VALIDATION) +} -} // anonymous namespace +DebugUtils* DebugUtils::get() { + assert_invariant(DebugUtils::mSingleton); + return DebugUtils::mSingleton; +} + +DebugUtils::~DebugUtils() { + if (mDebugMessenger) { + vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, VKALLOC); + } +} + +void DebugUtils::setName(VkObjectType type, uint64_t handle, char const* name) { + auto impl = DebugUtils::get(); + if (!impl->mEnabled) { + return; + } + VkDebugUtilsObjectNameInfoEXT const info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .pNext = nullptr, + .objectType = type, + .objectHandle = handle, + .pObjectName = name, + }; + vkSetDebugUtilsObjectNameEXT(impl->mDevice, &info); +} +#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS) using ImgUtil = VulkanImageUtility; @@ -151,61 +218,53 @@ 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), - mPipelineCache(&mResourceAllocator), - mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache), + mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(), + mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator), + mPipelineLayoutCache(mPlatform->getDevice(), &mResourceAllocator), + mPipelineCache(mPlatform->getDevice(), mAllocator), + mStagePool(mAllocator, &mCommands), + mFramebufferCache(mPlatform->getDevice()), + mSamplerCache(mPlatform->getDevice()), + mBlitter(mPlatform->getPhysicalDevice(), &mCommands), mReadPixels(mPlatform->getDevice()), + mDescriptorSetManager(mPlatform->getDevice(), &mResourceAllocator), mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) { +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) + DebugUtils::mSingleton = + new DebugUtils(mPlatform->getInstance(), mPlatform->getDevice(), &context); +#endif + #if FVK_ENABLED(FVK_DEBUG_VALIDATION) UTILS_UNUSED const PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback = vkCreateDebugReportCallbackEXT; - VkResult result; - if (mContext.isDebugUtilsSupported()) { - VkDebugUtilsMessengerCreateInfoEXT const createInfo = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - .pNext = nullptr, - .flags = 0, - .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT - | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, - .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT - | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, - .pfnUserCallback = debugUtilsCallback, - }; - result = vkCreateDebugUtilsMessengerEXT(mPlatform->getInstance(), &createInfo, VKALLOC, - &mDebugMessenger); - ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug messenger."); - } else if (createDebugReportCallback) { + if (!context.isDebugUtilsSupported() && createDebugReportCallback) { VkDebugReportCallbackCreateInfoEXT const cbinfo = { .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, .flags = VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT, .pfnCallback = debugReportCallback, }; - result = createDebugReportCallback(mPlatform->getInstance(), &cbinfo, VKALLOC, + VkResult result = createDebugReportCallback(mPlatform->getInstance(), &cbinfo, VKALLOC, &mDebugCallback); ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug callback."); } #endif + mTimestamps = std::make_unique(mPlatform->getDevice()); - mCommands = std::make_unique(mPlatform->getDevice(), - mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext, - &mResourceAllocator); - mCommands->setObserver(&mPipelineCache); - mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator); - // TOOD: move them all to be initialized by constructor - mStagePool.initialize(mAllocator, mCommands.get()); - mFramebufferCache.initialize(mPlatform->getDevice()); - mSamplerCache.initialize(mPlatform->getDevice()); + mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), + mContext, mAllocator, &mCommands, mStagePool); + mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands); - mEmptyTexture.reset(createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), - mContext, mAllocator, mCommands.get(), mStagePool)); + mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture, + mEmptyBufferObject); - mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView()); - mBlitter.initialize(mPlatform->getPhysicalDevice(), mPlatform->getDevice(), mAllocator, - mCommands.get(), mEmptyTexture.get()); + mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts) { + return mPipelineLayoutCache.getLayout(layouts); + }; } VulkanDriver::~VulkanDriver() noexcept = default; @@ -213,10 +272,47 @@ VulkanDriver::~VulkanDriver() noexcept = default; UTILS_NOINLINE Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& context, Platform::DriverConfig const& driverConfig) noexcept { +#if 0 + // this is useful for development, but too verbose even for debug builds + // For reference on a 64-bits machine in Release mode: + // VulkanSamplerGroup : 16 few + // HwStream : 24 few + // VulkanFence : 32 few + // VulkanProgram : 32 moderate + // VulkanIndexBuffer : 64 moderate + // VulkanBufferObject : 64 many + // -- less than or equal 64 bytes + // VulkanRenderPrimitive : 72 many + // VulkanVertexBufferInfo : 96 moderate + // VulkanSwapChain : 104 few + // VulkanTimerQuery : 160 few + // -- less than or equal 160 bytes + // VulkanVertexBuffer : 192 moderate + // VulkanTexture : 224 moderate + // VulkanRenderTarget : 312 few + // -- less than or equal to 312 bytes + + utils::slog.d + << "\nVulkanSwapChain: " << sizeof(VulkanSwapChain) + << "\nVulkanBufferObject: " << sizeof(VulkanBufferObject) + << "\nVulkanVertexBuffer: " << sizeof(VulkanVertexBuffer) + << "\nVulkanVertexBufferInfo: " << sizeof(VulkanVertexBufferInfo) + << "\nVulkanIndexBuffer: " << sizeof(VulkanIndexBuffer) + << "\nVulkanSamplerGroup: " << sizeof(VulkanSamplerGroup) + << "\nVulkanRenderPrimitive: " << sizeof(VulkanRenderPrimitive) + << "\nVulkanTexture: " << sizeof(VulkanTexture) + << "\nVulkanTimerQuery: " << sizeof(VulkanTimerQuery) + << "\nHwStream: " << sizeof(HwStream) + << "\nVulkanRenderTarget: " << sizeof(VulkanRenderTarget) + << "\nVulkanFence: " << sizeof(VulkanFence) + << "\nVulkanProgram: " << sizeof(VulkanProgram) + << utils::io::endl; +#endif + assert_invariant(platform); size_t defaultSize = FVK_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U; - Platform::DriverConfig validConfig{ - .handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize)}; + Platform::DriverConfig validConfig {driverConfig}; + validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize); return new VulkanDriver(platform, context, validConfig); } @@ -229,10 +325,14 @@ ShaderModel VulkanDriver::getShaderModel() const noexcept { } void VulkanDriver::terminate() { + delete mEmptyBufferObject; + delete mEmptyTexture; + // Command buffers should come first since it might have commands depending on resources that // are about to be destroyed. - mCommands.reset(); - mEmptyTexture.reset(); + mCommands.terminate(); + + mResourceManager.clear(); mTimestamps.reset(); mBlitter.terminate(); @@ -245,20 +345,29 @@ void VulkanDriver::terminate() { mPipelineCache.terminate(); mFramebufferCache.reset(); mSamplerCache.terminate(); + mDescriptorSetManager.terminate(); + mPipelineLayoutCache.terminate(); + +#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK) + mResourceAllocator.print(); +#endif vmaDestroyAllocator(mAllocator); if (mDebugCallback) { vkDestroyDebugReportCallbackEXT(mPlatform->getInstance(), mDebugCallback, VKALLOC); } - if (mDebugMessenger) { - vkDestroyDebugUtilsMessengerEXT(mPlatform->getInstance(), mDebugMessenger, VKALLOC); - } + +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) + assert_invariant(DebugUtils::mSingleton); + delete DebugUtils::mSingleton; +#endif + mPlatform->terminate(); } void VulkanDriver::tick(int) { - mCommands->updateFences(); + mCommands.updateFences(); } // Garbage collection should not occur too frequently, only about once per frame. Internally, the @@ -270,9 +379,16 @@ 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(); + mPipelineCache.gc(); + mDescriptorSetManager.gc(); + +#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK) + mResourceAllocator.print(); +#endif + FVK_SYSTRACE_END(); } void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) { @@ -293,7 +409,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(); } @@ -301,7 +417,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(); } @@ -309,26 +425,25 @@ 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(); } -void VulkanDriver::createSamplerGroupR(Handle sbh, uint32_t count) { +void VulkanDriver::createSamplerGroupR(Handle sbh, uint32_t count, + utils::FixedSizeString<32> debugName) { auto sg = mResourceAllocator.construct(sbh, count); mResourceManager.acquire(sg); } void VulkanDriver::createRenderPrimitiveR(Handle rph, Handle vbh, Handle ibh, - PrimitiveType pt, uint32_t offset, - uint32_t minIndex, uint32_t maxIndex, uint32_t count) { - auto rp = mResourceAllocator.construct(rph, &mResourceAllocator); + PrimitiveType pt) { + auto rp = mResourceAllocator.construct(rph, + &mResourceAllocator, pt, vbh, ibh); mResourceManager.acquire(rp); - VulkanDriver::setRenderPrimitiveBuffer(rph, vbh, ibh); - VulkanDriver::setRenderPrimitiveRange(rph, pt, offset, minIndex, maxIndex, count); } void VulkanDriver::destroyRenderPrimitive(Handle rph) { @@ -339,10 +454,26 @@ void VulkanDriver::destroyRenderPrimitive(Handle rph) { mResourceManager.release(rp); } -void VulkanDriver::createVertexBufferR(Handle vbh, uint8_t bufferCount, - uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes) { - auto vertexBuffer = mResourceAllocator.construct(vbh, mContext, mStagePool, - &mResourceAllocator, bufferCount, attributeCount, elementCount, attributes); +void VulkanDriver::createVertexBufferInfoR(Handle vbih, uint8_t bufferCount, + uint8_t attributeCount, AttributeArray attributes) { + auto vbi = mResourceAllocator.construct(vbih, + bufferCount, attributeCount, attributes); + mResourceManager.acquire(vbi); +} + +void VulkanDriver::destroyVertexBufferInfo(Handle vbih) { + if (!vbih) { + return; + } + auto vbi = mResourceAllocator.handle_cast(vbih); + mResourceManager.release(vbi); +} + + +void VulkanDriver::createVertexBufferR(Handle vbh, + uint32_t vertexCount, Handle vbih) { + auto vertexBuffer = mResourceAllocator.construct(vbh, + mContext, mStagePool, &mResourceAllocator, vertexCount, vbih); mResourceManager.acquire(vertexBuffer); } @@ -382,9 +513,6 @@ void VulkanDriver::destroyBufferObject(Handle boh) { return; } auto bufferObject = mResourceAllocator.handle_cast(boh); - if (bufferObject->bindingType == BufferObjectBinding::UNIFORM) { - mPipelineCache.unbindUniformBuffer(bufferObject->buffer.getGpuBuffer()); - } mResourceManager.release(bufferObject); } @@ -392,7 +520,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); } @@ -404,7 +532,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); } @@ -425,7 +553,8 @@ void VulkanDriver::destroyTexture(Handle th) { } void VulkanDriver::createProgramR(Handle ph, Program&& program) { - auto vkprogram = mResourceAllocator.construct(ph, mPlatform->getDevice(), program); + auto vkprogram + = mResourceAllocator.construct(ph, mPlatform->getDevice(), program); mResourceManager.acquire(vkprogram); } @@ -434,6 +563,7 @@ void VulkanDriver::destroyProgram(Handle ph) { return; } auto vkprogram = mResourceAllocator.handle_cast(ph); + mDescriptorSetManager.clearProgram(vkprogram); mResourceManager.release(vkprogram); } @@ -446,7 +576,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; @@ -498,7 +628,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); } @@ -516,7 +646,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); } @@ -527,7 +657,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); } @@ -540,7 +670,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); } @@ -548,6 +678,10 @@ void VulkanDriver::createTimerQueryR(Handle tqh, int) { // nothing to do, timer query was constructed in createTimerQueryS } +Handle VulkanDriver::createVertexBufferInfoS() noexcept { + return mResourceAllocator.allocHandle(); +} + Handle VulkanDriver::createVertexBufferS() noexcept { return mResourceAllocator.allocHandle(); } @@ -754,6 +888,7 @@ bool VulkanDriver::isFrameTimeSupported() { } bool VulkanDriver::isAutoDepthResolveSupported() { + // TODO: this could be supported with vk 1.2 or VK_KHR_depth_stencil_resolve return false; } @@ -761,14 +896,34 @@ bool VulkanDriver::isSRGBSwapChainSupported() { return mIsSRGBSwapChainSupported; } -bool VulkanDriver::isStereoSupported() { - return true; +bool VulkanDriver::isProtectedContentSupported() { + // the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported + return false; +} + +bool VulkanDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) { + switch (stereoscopicType) { + case backend::StereoscopicType::INSTANCED: + return true; + case backend::StereoscopicType::MULTIVIEW: + // TODO: implement multiview feature in Vulkan. + return false; + } } bool VulkanDriver::isParallelShaderCompileSupported() { return false; } +bool VulkanDriver::isDepthStencilResolveSupported() { + // TODO: apparently it could be supported in core 1.2 and/or with VK_KHR_depth_stencil_resolve + return false; +} + +bool VulkanDriver::isProtectedTexturesSupported() { + return false; +} + bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) { switch (workaround) { case Workaround::SPLIT_EASU: { @@ -855,13 +1010,12 @@ void VulkanDriver::setVertexBufferObject(Handle vbh, uint32_t in auto vb = mResourceAllocator.handle_cast(vbh); auto bo = mResourceAllocator.handle_cast(boh); assert_invariant(bo->bindingType == BufferObjectBinding::VERTEX); - - vb->setBuffer(bo, index); + vb->setBuffer(mResourceAllocator, bo, index); } 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); @@ -871,7 +1025,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); @@ -882,7 +1036,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 @@ -915,10 +1069,10 @@ void VulkanDriver::update3DImage(Handle th, uint32_t level, uint32_t void VulkanDriver::setupExternalImage(void* image) { } -bool VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { +TimerQueryResult VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { VulkanTimerQuery* vtq = mResourceAllocator.handle_cast(tqh); if (!vtq->isCompleted()) { - return false; + return TimerQueryResult::NOT_READY; } auto results = mTimestamps->getResult(vtq); @@ -928,7 +1082,7 @@ bool VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapse uint64_t available1 = results[3]; if (available0 == 0 || available1 == 0) { - return false; + return TimerQueryResult::NOT_READY; } ASSERT_POSTCONDITION(timestamp1 >= timestamp0, "Timestamps are not monotonically increasing."); @@ -940,7 +1094,7 @@ bool VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapse float const period = mContext.getPhysicalDeviceLimits().timestampPeriod; uint64_t delta = uint64_t(float(timestamp1 - timestamp0) * period); *elapsedTime = delta; - return true; + return TimerQueryResult::AVAILABLE; } void VulkanDriver::setExternalImage(Handle th, void* image) { @@ -952,10 +1106,43 @@ void VulkanDriver::setExternalImagePlane(Handle th, void* image, uint void VulkanDriver::setExternalStream(Handle th, Handle sh) { } -void VulkanDriver::generateMipmaps(Handle th) { } +void VulkanDriver::generateMipmaps(Handle th) { + auto* const t = mResourceAllocator.handle_cast(th); + assert_invariant(t); -bool VulkanDriver::canGenerateMipmaps() { - return false; + int32_t layerCount = int32_t(t->depth); + if (t->target == SamplerType::SAMPLER_CUBEMAP_ARRAY || + t->target == SamplerType::SAMPLER_CUBEMAP) { + layerCount *= 6; + } + + // FIXME: the loop below can perform many layout transitions and back. We should be + // able to optimize that. + + uint8_t level = 0; + int32_t srcw = int32_t(t->width); + int32_t srch = int32_t(t->height); + do { + int32_t const dstw = std::max(srcw >> 1, 1); + int32_t const dsth = std::max(srch >> 1, 1); + const VkOffset3D srcOffsets[2] = {{ 0, 0, 0 }, { srcw, srch, 1 }}; + const VkOffset3D dstOffsets[2] = {{ 0, 0, 0 }, { dstw, dsth, 1 }}; + + // TODO: there should be a way to do this using layerCount in vkBlitImage + // TODO: vkBlitImage should be able to handle 3D textures too + for (int32_t layer = 0; layer < layerCount; layer++) { + mBlitter.blit(VK_FILTER_LINEAR, + { .texture = t, .level = uint8_t(level + 1), .layer = (uint16_t)layer }, + dstOffsets, + { .texture = t, .level = uint8_t(level ), .layer = (uint16_t)layer }, + srcOffsets); + } + + level++; + srcw = dstw; + srch = dsth; + } while ((srcw > 1 || srch > 1) && level < t->levels); + t->setPrimaryRange(0, t->levels - 1); } void VulkanDriver::updateSamplerGroup(Handle sbh, @@ -987,7 +1174,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP VulkanRenderTarget* const rt = mResourceAllocator.handle_cast(rth); const VkExtent2D extent = rt->getExtent(); - assert_invariant(extent.width > 0 && extent.height > 0); + assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0); // Filament has the expectation that the contents of the swap chain are not preserved on the // first render pass. Note however that its contents are often preserved on subsequent render @@ -1014,7 +1201,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 @@ -1139,22 +1326,14 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP } VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey); - // Assign a label to the framebuffer for debugging purposes. - #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - if (UTILS_UNLIKELY(mContext.isDebugUtilsSupported())) { - auto const topMarker = mCommands->getTopGroupMarker(); - if (!topMarker.empty()) { - const VkDebugUtilsObjectNameInfoEXT info = { - VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, - nullptr, - VK_OBJECT_TYPE_FRAMEBUFFER, - reinterpret_cast(vkfb), - topMarker.c_str(), - }; - vkSetDebugUtilsObjectNameEXT(mPlatform->getDevice(), &info); - } +// 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(); + if (!topMarker.empty()) { + DebugUtils::setName(VK_OBJECT_TYPE_FRAMEBUFFER, reinterpret_cast(vkfb), + topMarker.c_str()); } - #endif +#endif // The current command buffer now owns a reference to the render target and its attachments. // Note that we must acquire parent textures, not sidecars. @@ -1238,7 +1417,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); @@ -1272,12 +1451,7 @@ void VulkanDriver::endRenderPass(int) { 0, 1, &barrier, 0, nullptr, 0, nullptr); } - if (mCurrentRenderPass.currentSubpass > 0) { - for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) { - mPipelineCache.bindInputAttachment(i, {}); - } - mCurrentRenderPass.currentSubpass = 0; - } + mDescriptorSetManager.clearState(); mCurrentRenderPass.renderTarget = nullptr; mCurrentRenderPass.renderPass = VK_NULL_HANDLE; FVK_SYSTRACE_END(); @@ -1291,41 +1465,17 @@ 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); - for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) { - if ((1 << i) & mCurrentRenderPass.params.subpassMask) { - VulkanAttachment subpassInput = renderTarget->getColor(i); - VkDescriptorImageInfo info = { - .imageView = subpassInput.getImageView(VK_IMAGE_ASPECT_COLOR_BIT), - .imageLayout = ImgUtil::getVkLayout(subpassInput.getLayout()), - }; - mPipelineCache.bindInputAttachment(i, info); - } + if (mCurrentRenderPass.params.subpassMask & 0x1) { + VulkanAttachment subpassInput = renderTarget->getColor(0); + mDescriptorSetManager.updateInputAttachment({}, subpassInput); } } -void VulkanDriver::setRenderPrimitiveBuffer(Handle rph, - Handle vbh, Handle ibh) { - auto primitive = mResourceAllocator.handle_cast(rph); - primitive->setBuffers(mResourceAllocator.handle_cast(vbh), - mResourceAllocator.handle_cast(ibh)); -} - -void VulkanDriver::setRenderPrimitiveRange(Handle rph, - PrimitiveType pt, uint32_t offset, - uint32_t minIndex, uint32_t maxIndex, uint32_t count) { - auto& primitive = *mResourceAllocator.handle_cast(rph); - primitive.setPrimitiveType(pt); - primitive.offset = offset * primitive.indexBuffer->elementSize; - primitive.count = count; - primitive.minIndex = minIndex; - primitive.maxIndex = maxIndex > minIndex ? maxIndex : primitive.maxVertexCount - 1; -} - void VulkanDriver::makeCurrent(Handle drawSch, Handle readSch) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("makeCurrent"); @@ -1362,25 +1512,24 @@ void VulkanDriver::commit(Handle sch) { void VulkanDriver::bindUniformBuffer(uint32_t index, Handle boh) { auto* bo = mResourceAllocator.handle_cast(boh); - const VkDeviceSize offset = 0; - const VkDeviceSize size = VK_WHOLE_SIZE; - mPipelineCache.bindUniformBufferObject((uint32_t) index, bo, offset, size); + VkDeviceSize const offset = 0; + VkDeviceSize const size = VK_WHOLE_SIZE; + mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size); } void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index, Handle boh, uint32_t offset, uint32_t size) { - assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE || - bindingType == BufferObjectBinding::UNIFORM); + assert_invariant(bindingType == BufferObjectBinding::UNIFORM); // TODO: implement BufferObjectBinding::SHADER_STORAGE case auto* bo = mResourceAllocator.handle_cast(boh); - mPipelineCache.bindUniformBufferObject((uint32_t)index, bo, offset, size); + mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size); } void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) { - // TODO: implement unbindBuffer() + mDescriptorSetManager.clearBuffer((uint32_t) index); } void VulkanDriver::bindSamplers(uint32_t index, Handle sbh) { @@ -1390,14 +1539,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); @@ -1405,7 +1554,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(); @@ -1418,7 +1567,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), @@ -1436,132 +1585,206 @@ void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh, scheduleDestroy(std::move(p)); } -void VulkanDriver::blit(TargetBufferFlags buffers, Handle dst, Viewport dstRect, - Handle src, Viewport srcRect, SamplerMagFilter filter) { +void VulkanDriver::resolve( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, + Handle src, uint8_t dstLevel, uint8_t dstLayer) { + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("resolve"); + + ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE, + "resolve() cannot be invoked inside a render pass."); + + auto* const srcTexture = mResourceAllocator.handle_cast(src); + auto* const dstTexture = mResourceAllocator.handle_cast(dst); + assert_invariant(srcTexture); + assert_invariant(dstTexture); + + ASSERT_PRECONDITION( + dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height, + "invalid resolve: src and dst sizes don't match"); + + ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1, + "invalid resolve: src.samples=%u, dst.samples=%u", + +srcTexture->samples, +dstTexture->samples); + + ASSERT_PRECONDITION(srcTexture->format == dstTexture->format, + "src and dst texture format don't match"); + + ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format), + "can't resolve depth formats"); + + ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format), + "can't resolve stencil formats"); + + ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST), + "texture doesn't have BLIT_DST"); + + ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC), + "texture doesn't have BLIT_SRC"); + + mBlitter.resolve( + { .texture = dstTexture, .level = dstLevel, .layer = dstLayer }, + { .texture = srcTexture, .level = srcLevel, .layer = srcLayer }); + + FVK_SYSTRACE_END(); +} + +void VulkanDriver::blit( + Handle dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin, + Handle src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin, + math::uint2 size) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("blit"); - assert_invariant(mCurrentRenderPass.renderPass == VK_NULL_HANDLE); + ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE, + "blit() cannot be invoked inside a render pass."); - // blit operation only support COLOR0 color buffer - assert_invariant( - !(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0))); + auto* const srcTexture = mResourceAllocator.handle_cast(src); + auto* const dstTexture = mResourceAllocator.handle_cast(dst); - if (UTILS_UNLIKELY(mCurrentRenderPass.renderPass)) { - utils::slog.e << "Blits cannot be invoked inside a render pass." << utils::io::endl; - return; - } + ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST), + "texture doesn't have BLIT_DST"); + + ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC), + "texture doesn't have BLIT_SRC"); + + ASSERT_PRECONDITION(srcTexture->format == dstTexture->format, + "src and dst texture format don't match"); + + // The Y inversion below makes it so that Vk matches GL and Metal. + + auto const srcLeft = int32_t(srcOrigin.x); + auto const dstLeft = int32_t(dstOrigin.x); + auto const srcTop = int32_t(srcTexture->height - (srcOrigin.y + size.y)); + auto const dstTop = int32_t(dstTexture->height - (dstOrigin.y + size.y)); + auto const srcRight = int32_t(srcOrigin.x + size.x); + auto const dstRight = int32_t(dstOrigin.x + size.x); + auto const srcBottom = int32_t(srcTop + size.y); + auto const dstBottom = int32_t(dstTop + size.y); + VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }}; + VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }}; + + // no scaling guaranteed + mBlitter.blit(VK_FILTER_NEAREST, + { .texture = dstTexture, .level = dstLevel, .layer = dstLayer }, dstOffsets, + { .texture = srcTexture, .level = srcLevel, .layer = srcLayer }, srcOffsets); + + FVK_SYSTRACE_END(); +} + +void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers, + Handle dst, Viewport dstRect, + Handle src, Viewport srcRect, + SamplerMagFilter filter) { + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("blitDEPRECATED"); + + // Note: blitDEPRECATED is only used for Renderer::copyFrame() + + ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE, + "blitDEPRECATED() cannot be invoked inside a render pass."); + + ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0, + "blitDEPRECATED only supports COLOR0"); + + ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 && + dstRect.left >= 0 && dstRect.bottom >= 0, + "Source and destination rects must be positive."); VulkanRenderTarget* dstTarget = mResourceAllocator.handle_cast(dst); VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast(src); - VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; + VkFilter const vkfilter = (filter == SamplerMagFilter::NEAREST) ? + VK_FILTER_NEAREST : VK_FILTER_LINEAR; // The Y inversion below makes it so that Vk matches GL and Metal. - const VkExtent2D srcExtent = srcTarget->getExtent(); - const int32_t srcLeft = srcRect.left; - const int32_t srcTop = srcExtent.height - srcRect.bottom - srcRect.height; - const int32_t srcRight = srcRect.left + srcRect.width; - const int32_t srcBottom = srcTop + srcRect.height; - const VkOffset3D srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }}; - - const VkExtent2D dstExtent = dstTarget->getExtent(); - const int32_t dstLeft = dstRect.left; - const int32_t dstTop = dstExtent.height - dstRect.bottom - dstRect.height; - const int32_t dstRight = dstRect.left + dstRect.width; - const int32_t dstBottom = dstTop + dstRect.height; - const VkOffset3D dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }}; - - if (any(buffers & TargetBufferFlags::DEPTH) && srcTarget->hasDepth() && dstTarget->hasDepth()) { - mBlitter.blitDepth({dstTarget, dstOffsets, srcTarget, srcOffsets}); - } + VkExtent2D const srcExtent = srcTarget->getExtent(); + VkExtent2D const dstExtent = dstTarget->getExtent(); + + auto const dstLeft = int32_t(dstRect.left); + auto const srcLeft = int32_t(srcRect.left); + auto const dstTop = int32_t(dstExtent.height - (dstRect.bottom + dstRect.height)); + auto const srcTop = int32_t(srcExtent.height - (srcRect.bottom + srcRect.height)); + auto const dstRight = int32_t(dstRect.left + dstRect.width); + auto const srcRight = int32_t(srcRect.left + srcRect.width); + auto const dstBottom = int32_t(dstTop + dstRect.height); + auto const srcBottom = int32_t(srcTop + srcRect.height); + VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }}; + VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }}; + + mBlitter.blit(vkfilter, + dstTarget->getColor(0), dstOffsets, + srcTarget->getColor(0), srcOffsets); - if (any(buffers & TargetBufferFlags::COLOR0)) { - mBlitter.blitColor({ dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, int(0) }); - } FVK_SYSTRACE_END(); } -void VulkanDriver::draw(PipelineState pipelineState, Handle rph, - const uint32_t instanceCount) { +void VulkanDriver::bindPipeline(PipelineState pipelineState) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("draw"); - VulkanCommandBuffer* commands = &mCommands->get(); - VkCommandBuffer cmdbuffer = commands->buffer(); - const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast(rph); + VulkanCommandBuffer* commands = &mCommands.get(); + const VulkanVertexBufferInfo& vbi = + *mResourceAllocator.handle_cast(pipelineState.vertexBufferInfo); Handle programHandle = pipelineState.program; RasterState rasterState = pipelineState.rasterState; PolygonOffset depthOffset = pipelineState.polygonOffset; - Viewport const& scissorBox = pipelineState.scissor; auto* program = mResourceAllocator.handle_cast(programHandle); commands->acquire(program); - commands->acquire(prim.indexBuffer); - commands->acquire(prim.vertexBuffer); - - // If this is a debug build, validate the current shader. -#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) - if (program->bundle.vertex == VK_NULL_HANDLE || program->bundle.fragment == VK_NULL_HANDLE) { - utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl; - } -#endif // Update the VK raster state. const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget; - auto vkraster = mPipelineCache.getCurrentRasterState(); - vkraster.cullMode = getCullMode(rasterState.culling); - vkraster.frontFace = getFrontFace(rasterState.inverseFrontFaces); - vkraster.depthBiasEnable = (depthOffset.constant || depthOffset.slope) ? true : false; - vkraster.depthBiasConstantFactor = depthOffset.constant; - vkraster.depthBiasSlopeFactor = depthOffset.slope; - vkraster.blendEnable = rasterState.hasBlending(); - vkraster.srcColorBlendFactor = getBlendFactor(rasterState.blendFunctionSrcRGB); - vkraster.dstColorBlendFactor = getBlendFactor(rasterState.blendFunctionDstRGB); - vkraster.colorBlendOp = rasterState.blendEquationRGB; - vkraster.srcAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionSrcAlpha); - vkraster.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha); - vkraster.alphaBlendOp = rasterState.blendEquationAlpha; - vkraster.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0); - vkraster.depthWriteEnable = rasterState.depthWrite; - vkraster.depthCompareOp = rasterState.depthFunc; - vkraster.rasterizationSamples = rt->getSamples(); - vkraster.alphaToCoverageEnable = rasterState.alphaToCoverage; - vkraster.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass); - mPipelineCache.setCurrentRasterState(vkraster); + VulkanPipelineCache::RasterState const vulkanRasterState{ + .cullMode = getCullMode(rasterState.culling), + .frontFace = getFrontFace(rasterState.inverseFrontFaces), + .depthBiasEnable = (depthOffset.constant || depthOffset.slope) ? true : false, + .blendEnable = rasterState.hasBlending(), + .depthWriteEnable = rasterState.depthWrite, + .alphaToCoverageEnable = rasterState.alphaToCoverage, + .srcColorBlendFactor = getBlendFactor(rasterState.blendFunctionSrcRGB), + .dstColorBlendFactor = getBlendFactor(rasterState.blendFunctionDstRGB), + .srcAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionSrcAlpha), + .dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha), + .colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0), + .rasterizationSamples = rt->getSamples(), + .colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass), + .colorBlendOp = rasterState.blendEquationRGB, + .alphaBlendOp = rasterState.blendEquationAlpha, + .depthCompareOp = rasterState.depthFunc, + .depthBiasConstantFactor = depthOffset.constant, + .depthBiasSlopeFactor = depthOffset.slope + }; + + // unfortunately in Vulkan the topology is per pipeline + VkPrimitiveTopology const topology = + VulkanPipelineCache::getPrimitiveTopology(pipelineState.primitiveType); // Declare fixed-size arrays that get passed to the pipeCache and to vkCmdBindVertexBuffers. - uint32_t const bufferCount = prim.vertexBuffer->attributes.size(); - VkVertexInputAttributeDescription const* attribDesc = prim.vertexBuffer->getAttribDescriptions(); - VkVertexInputBindingDescription const* bufferDesc = prim.vertexBuffer->getBufferDescriptions(); - VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers(); - VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets(); + VkVertexInputAttributeDescription const* attribDesc = vbi.getAttribDescriptions(); + VkVertexInputBindingDescription const* bufferDesc = vbi.getBufferDescriptions(); // Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls. mPipelineCache.bindProgram(program); - mPipelineCache.bindRasterState(mPipelineCache.getCurrentRasterState()); - mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology); - mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount); + mPipelineCache.bindRasterState(vulkanRasterState); + mPipelineCache.bindPrimitiveTopology(topology); + mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi.getAttributeCount()); // Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding), // where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract // Filament concept used to form groups of samplers. - VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {}; - VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr}; - auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex(); - VulkanPipelineCache::UsageFlags usage = program->getUsage(); +#if FVK_ENABLED_DEBUG_SAMPLER_NAME + auto const& bindingToName = program->getBindingToName(); +#endif - UTILS_NOUNROLL - for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) { + for (auto binding: program->getBindings()) { uint16_t const indexPair = bindingToSamplerIndex[binding]; - if (indexPair == 0xffff) { - usage = VulkanPipelineCache::disableUsageFlags(binding, usage); continue; } @@ -1570,90 +1793,62 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd]; if (!vksb) { - usage = VulkanPipelineCache::disableUsageFlags(binding, usage); continue; } SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd; if (UTILS_UNLIKELY(!boundSampler->t)) { - usage = VulkanPipelineCache::disableUsageFlags(binding, usage); continue; } - VulkanTexture* texture = mResourceAllocator.handle_cast(boundSampler->t); - VkImageViewType const expectedType = texture->getViewType(); // TODO: can this uninitialized check be checked in a higher layer? // This fallback path is very flaky because the dummy texture might not have // matching characteristics. (e.g. if the missing texture is a 3D texture) if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) { #if FVK_ENABLED(FVK_DEBUG_TEXTURE) - utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'"; + utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'"; utils::slog.w << " in material '" << program->name.c_str() << "'"; - utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl; + utils::slog.w << " at binding point " << +binding << utils::io::endl; #endif - texture = mEmptyTexture.get(); + texture = mEmptyTexture; } - SamplerParams const& samplerParams = boundSampler->s; - VkSampler const vksampler = mSamplerCache.getSampler(samplerParams); - VkImageView imageView = VK_NULL_HANDLE; - VkImageSubresourceRange const range = texture->getPrimaryViewRange(); - if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) && - expectedType == VK_IMAGE_VIEW_TYPE_2D) { - // If the sampler is part of a mipmapped depth texture, where one of the level *can* be - // an attachment, then the sampler for this texture has the same view properties as a - // view for an attachment. Therefore, we can use getAttachmentView to get a - // corresponding VkImageView. - imageView = texture->getAttachmentView(range); - } else { - imageView = texture->getViewForType(range, expectedType); - } +#if FVK_ENABLED_DEBUG_SAMPLER_NAME + VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER, + reinterpret_cast(vksampler), bindingToName[binding].c_str()); + VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER, + reinterpret_cast(samplerInfo.sampler), bindingToName[binding].c_str()); +#endif - samplerInfo[binding] = { - .sampler = vksampler, - .imageView = imageView, - .imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout()) - }; - samplerTextures[binding] = texture; + VkSampler const vksampler = mSamplerCache.getSampler(boundSampler->s); + + mDescriptorSetManager.updateSampler({}, binding, texture, vksampler); } - mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage); + mPipelineCache.bindLayout(mDescriptorSetManager.bind(commands, program, mGetPipelineFunction)); + mPipelineCache.bindPipeline(commands); + FVK_SYSTRACE_END(); +} - // 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 - // message since the validation layers already do so. - if (!mPipelineCache.bindDescriptors(cmdbuffer)) { - return; - } +void VulkanDriver::bindRenderPrimitive(Handle rph) { + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("bindRenderPrimitive"); - // Set scissoring. - // clamp left-bottom to 0,0 and avoid overflows - constexpr int32_t maxvali = std::numeric_limits::max(); - constexpr uint32_t maxvalu = std::numeric_limits::max(); - int32_t l = scissorBox.left; - int32_t b = scissorBox.bottom; - uint32_t w = std::min(maxvalu, scissorBox.width); - uint32_t h = std::min(maxvalu, scissorBox.height); - int32_t r = (l > int32_t(maxvalu - w)) ? maxvali : l + int32_t(w); - int32_t t = (b > int32_t(maxvalu - h)) ? maxvali : b + int32_t(h); - l = std::max(0, l); - b = std::max(0, b); - assert_invariant(r >= l && t >= b); - VkRect2D scissor{ - .offset = { l, b }, - .extent = { uint32_t(r - l), uint32_t(t - b) } - }; + VulkanCommandBuffer* commands = &mCommands.get(); + VkCommandBuffer cmdbuffer = commands->buffer(); + const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast(rph); + commands->acquire(prim.indexBuffer); + commands->acquire(prim.vertexBuffer); - rt->transformClientRectToPlatform(&scissor); - mPipelineCache.bindScissor(cmdbuffer, scissor); + // This *must* match the VulkanVertexBufferInfo that was bound in bindPipeline(). But we want + // to allow to call this before bindPipeline(), so the validation can only happen in draw() + VulkanVertexBufferInfo const* const vbi = + mResourceAllocator.handle_cast(prim.vertexBuffer->vbih); - // Bind a new pipeline if the pipeline state changed. - // If allocation failed, skip the draw call and bail. We do not emit an error since the - // validation layer will already do so. - if (!mPipelineCache.bindPipeline(commands)) { - return; - } + uint32_t const bufferCount = vbi->getAttributeCount(); + VkDeviceSize const* offsets = vbi->getOffsets(); + VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers(); // Next bind the vertex buffers and index buffer. One potential performance improvement is to // avoid rebinding these if they are already bound, but since we do not (yet) support subranges @@ -1662,28 +1857,78 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r vkCmdBindIndexBuffer(cmdbuffer, prim.indexBuffer->buffer.getGpuBuffer(), 0, prim.indexBuffer->indexType); + FVK_SYSTRACE_END(); +} + +void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("draw2"); + + VulkanCommandBuffer& commands = mCommands.get(); + VkCommandBuffer cmdbuffer = commands.buffer(); + + // Bind "dynamic" UBOs if they need to change. + mDescriptorSetManager.dynamicBind(&commands, {}); + // Finally, make the actual draw call. TODO: support subranges - const uint32_t indexCount = prim.count; - const uint32_t firstIndex = prim.offset / prim.indexBuffer->elementSize; + const uint32_t firstIndex = indexOffset; const int32_t vertexOffset = 0; const uint32_t firstInstId = 0; vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId); + FVK_SYSTRACE_END(); } +void VulkanDriver::draw(PipelineState state, Handle rph, + uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) { + VulkanRenderPrimitive* const rp = mResourceAllocator.handle_cast(rph); + state.primitiveType = rp->type; + state.vertexBufferInfo = rp->vertexBuffer->vbih; + bindPipeline(state); + bindRenderPrimitive(rph); + draw2(indexOffset, indexCount, instanceCount); +} + void VulkanDriver::dispatchCompute(Handle program, math::uint3 workGroupCount) { // FIXME: implement me } +void VulkanDriver::scissor(Viewport scissorBox) { + VulkanCommandBuffer& commands = mCommands.get(); + VkCommandBuffer cmdbuffer = commands.buffer(); + + // Set scissoring. + // clamp left-bottom to 0,0 and avoid overflows + constexpr int32_t maxvali = std::numeric_limits::max(); + constexpr uint32_t maxvalu = std::numeric_limits::max(); + int32_t l = scissorBox.left; + int32_t b = scissorBox.bottom; + uint32_t w = std::min(maxvalu, scissorBox.width); + uint32_t h = std::min(maxvalu, scissorBox.height); + int32_t r = (l > int32_t(maxvalu - w)) ? maxvali : l + int32_t(w); + int32_t t = (b > int32_t(maxvalu - h)) ? maxvali : b + int32_t(h); + l = std::max(0, l); + b = std::max(0, b); + assert_invariant(r >= l && t >= b); + VkRect2D scissor{ + .offset = { l, b }, + .extent = { uint32_t(r - l), uint32_t(t - b) } + }; + + const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget; + rt->transformClientRectToPlatform(&scissor); + mPipelineCache.bindScissor(cmdbuffer, scissor); +} + 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 16fa3a1b01c..fca5c45c5be 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -28,6 +28,8 @@ #include "VulkanSamplerCache.h" #include "VulkanStagePool.h" #include "VulkanUtility.h" +#include "caching/VulkanDescriptorSetManager.h" +#include "caching/VulkanPipelineLayoutCache.h" #include "DriverBase.h" #include "private/backend/Driver.h" @@ -45,7 +47,33 @@ class VulkanDriver final : public DriverBase { static Driver* create(VulkanPlatform* platform, VulkanContext const& context, Platform::DriverConfig const& driverConfig) noexcept; +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) + // Encapsulates the VK_EXT_debug_utils extension. In particular, we use + // vkSetDebugUtilsObjectNameEXT and vkCreateDebugUtilsMessengerEXT + class DebugUtils { + public: + static void setName(VkObjectType type, uint64_t handle, char const* name); + + private: + static DebugUtils* get(); + + DebugUtils(VkInstance instance, VkDevice device, VulkanContext const* context); + ~DebugUtils(); + + VkInstance const mInstance; + VkDevice const mDevice; + bool const mEnabled; + VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE; + + static DebugUtils* mSingleton; + + friend class VulkanDriver; + }; +#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) + private: + static constexpr uint8_t MAX_SAMPLER_BINDING_COUNT = Program::SAMPLER_BINDING_COUNT; + void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override; @@ -77,25 +105,20 @@ class VulkanDriver final : public DriverBase { VulkanDriver& operator=(VulkanDriver const&) = delete; private: - inline void setRenderPrimitiveBuffer(Handle rph, Handle vbh, - Handle ibh); - - inline void setRenderPrimitiveRange(Handle rph, PrimitiveType pt, - uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count); - void collectGarbage(); VulkanPlatform* mPlatform = nullptr; - std::unique_ptr mCommands; std::unique_ptr mTimestamps; - std::unique_ptr mEmptyTexture; + + // Placeholder resources + VulkanTexture* mEmptyTexture; + VulkanBufferObject* mEmptyBufferObject; VulkanSwapChain* mCurrentSwapChain = nullptr; VulkanRenderTarget* mDefaultRenderTarget = nullptr; VulkanRenderPass mCurrentRenderPass = {}; VmaAllocator mAllocator = VK_NULL_HANDLE; VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE; - VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE; VulkanContext mContext = {}; VulkanResourceAllocator mResourceAllocator; @@ -105,13 +128,18 @@ class VulkanDriver final : public DriverBase { // thread. VulkanThreadSafeResourceManager mThreadSafeResourceManager; + VulkanCommands mCommands; + VulkanPipelineLayoutCache mPipelineLayoutCache; VulkanPipelineCache mPipelineCache; VulkanStagePool mStagePool; VulkanFboCache mFramebufferCache; VulkanSamplerCache mSamplerCache; VulkanBlitter mBlitter; - VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {}; + VulkanSamplerGroup* mSamplerBindings[MAX_SAMPLER_BINDING_COUNT] = {}; VulkanReadPixels mReadPixels; + VulkanDescriptorSetManager mDescriptorSetManager; + + VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction; bool const mIsSRGBSwapChainSupported; }; diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index 0546ffd04fb..f4b222b1545 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(), @@ -238,7 +239,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { .format = config.colorFormat[i], .samples = (VkSampleCountFlagBits) config.samples, .loadOp = clear ? kClear : (discard ? kDontCare : kKeep), - .storeOp = config.samples == 1 ? kEnableStore : kDisableStore, + .storeOp = kEnableStore, .stencilLoadOp = kDontCare, .stencilStoreOp = kDisableStore, .initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear) 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/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index ac6b4ccea17..9d615ce5151 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -16,26 +16,33 @@ #include "VulkanHandles.h" +#include "VulkanDriver.h" #include "VulkanConstants.h" +#include "VulkanDriver.h" #include "VulkanMemory.h" +#include "VulkanUtility.h" +#include "spirv/VulkanSpirvUtils.h" +#include "utils/Log.h" #include -#include +#include // ASSERT_POSTCONDITION using namespace bluevk; namespace filament::backend { -static void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) { +namespace { + +void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) { rect->offset.y = framebufferHeight - rect->offset.y - rect->extent.height; } -static void flipVertically(VkViewport* rect, uint32_t framebufferHeight) { +void flipVertically(VkViewport* rect, uint32_t framebufferHeight) { rect->y = framebufferHeight - rect->y - rect->height; } -static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) { +void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) { int32_t x = std::max(rect->offset.x, 0); int32_t y = std::max(rect->offset.y, 0); int32_t right = std::min(rect->offset.x + (int32_t) rect->extent.width, (int32_t) fbWidth); @@ -46,63 +53,189 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig rect->extent.height = std::max(top - y, 0); } -VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept +template +static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) { + Bitmask ret = 0; + if (flags & ShaderStageFlags2::VERTEX) { + ret |= (getVertexStage() << binding); + } + if (flags & ShaderStageFlags2::FRAGMENT) { + ret |= (getFragmentStage() << binding); + } + return ret; +} + +UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) { + // NOTE: if you modify this function, you also need to modify getShaderStageFlags. + assert_invariant(binding < MAX_SAMPLER_COUNT); + if (any(flags & ShaderStageFlags::VERTEX)) { + src.set(binding); + } + if (any(flags & ShaderStageFlags::FRAGMENT)) { + src.set(MAX_SAMPLER_COUNT + binding); + } + // TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags + // has 186 bits (MAX_SAMPLER_COUNT * 3) + // assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT))); + return src; +} + +constexpr decltype(VulkanProgram::MAX_SHADER_MODULES) MAX_SHADER_MODULES = + VulkanProgram::MAX_SHADER_MODULES; + +using LayoutDescriptionList = VulkanProgram::LayoutDescriptionList; + +template +void addDescriptors(Bitmask mask, + utils::FixedCapacityVector& outputList) { + constexpr uint8_t MODULE_OFFSET = (sizeof(Bitmask) * 8) / MAX_SHADER_MODULES; + for (uint8_t i = 0; i < MODULE_OFFSET; ++i) { + bool const hasVertex = (mask & (1ULL << i)) != 0; + bool const hasFragment = (mask & (1ULL << (MODULE_OFFSET + i))) != 0; + if (!hasVertex && !hasFragment) { + continue; + } + + DescriptorSetLayoutBinding binding{ + .binding = i, + .flags = DescriptorFlags::NONE, + .count = 0,// This is always 0 for now as we pass the size of the UBOs in the Driver API + // instead. + }; + if (hasVertex) { + binding.stageFlags = ShaderStageFlags2::VERTEX; + } + if (hasFragment) { + binding.stageFlags = static_cast( + binding.stageFlags | ShaderStageFlags2::FRAGMENT); + } + if constexpr (std::is_same_v) { + binding.type = DescriptorType::UNIFORM_BUFFER; + } else if constexpr (std::is_same_v) { + binding.type = DescriptorType::SAMPLER; + } else if constexpr (std::is_same_v) { + binding.type = DescriptorType::INPUT_ATTACHMENT; + } + outputList.push_back(binding); + } +} + +inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device, + VkDescriptorSetLayoutCreateInfo const& info) { + VkDescriptorSetLayout layout; + vkCreateDescriptorSetLayout(device, &info, VKALLOC, &layout); + return layout; +} + +} // anonymous namespace + + +VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info, + Bitmask const& bitmask) + : VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), + mDevice(device), + vklayout(createDescriptorSetLayout(device, info)), + bitmask(bitmask), + bindings(getBindings(bitmask)), + count(Count::fromLayoutBitmask(bitmask)) { +} + +VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() { + vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC); +} + +VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept : HwProgram(builder.getName()), VulkanResource(VulkanResourceType::PROGRAM), - mInfo(new PipelineInfo(builder.getSpecializationConstants().size())), + mInfo(new PipelineInfo()), mDevice(device) { - auto& blobs = builder.getShadersSource(); + + constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES; + constexpr uint8_t SAMPLER_MODULE_OFFSET = (sizeof(SamplerBitmask) * 8) / MAX_SHADER_MODULES; + constexpr uint8_t INPUT_ATTACHMENT_MODULE_OFFSET = + (sizeof(InputAttachmentBitmask) * 8) / MAX_SHADER_MODULES; + + Program::ShaderSource const& blobs = builder.getShadersSource(); auto& modules = mInfo->shaders; + + auto const& specializationConstants = builder.getSpecializationConstants(); + + std::vector shader; + + // TODO: this will be moved out of the shader as the descriptor set layout will be provided by + // Filament instead of parsed from the shaders. See [GDSR] in VulkanDescriptorSetManager.h + UniformBufferBitmask uboMask = 0; + SamplerBitmask samplerMask = 0; + InputAttachmentBitmask inputAttachmentMask = 0; + + static_assert(static_cast(0) == ShaderStage::VERTEX && + static_cast(1) == ShaderStage::FRAGMENT && + MAX_SHADER_MODULES == 2); + for (size_t i = 0; i < MAX_SHADER_MODULES; i++) { - const auto& blob = blobs[i]; - uint32_t* data = (uint32_t*)blob.data(); + Program::ShaderBlob const& blob = blobs[i]; + + uint32_t* data = (uint32_t*) blob.data(); + size_t dataSize = blob.size(); + + if (!specializationConstants.empty()) { + workaroundSpecConstant(blob, specializationConstants, shader); + data = (uint32_t*) shader.data(); + dataSize = shader.size() * 4; + } + + auto const [ubo, sampler, inputAttachment] = getProgramBindings(blob); + uboMask |= (static_cast(ubo) << (UBO_MODULE_OFFSET * i)); + samplerMask |= (static_cast(sampler) << (SAMPLER_MODULE_OFFSET * i)); + inputAttachmentMask |= (static_cast(inputAttachment) + << (INPUT_ATTACHMENT_MODULE_OFFSET * i)); + VkShaderModule& module = modules[i]; VkShaderModuleCreateInfo moduleInfo = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .codeSize = blob.size(), + .codeSize = dataSize, .pCode = data, }; VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module); ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module."); - } - - // Note that bools are 4-bytes in Vulkan - // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html - constexpr uint32_t const CONSTANT_SIZE = 4; - // populate the specialization constants requirements right now - auto const& specializationConstants = builder.getSpecializationConstants(); - uint32_t const specConstCount = static_cast(specializationConstants.size()); - char* specData = mInfo->specConstData.get(); - if (specConstCount > 0) { - mInfo->specializationInfo = { - .mapEntryCount = specConstCount, - .pMapEntries = mInfo->specConsts.data(), - .dataSize = specConstCount * CONSTANT_SIZE, - .pData = specData, - }; - } - for (uint32_t i = 0; i < specConstCount; ++i) { - uint32_t const offset = i * CONSTANT_SIZE; - mInfo->specConsts[i] = { - .constantID = specializationConstants[i].id, - .offset = offset, - .size = CONSTANT_SIZE, - }; - using SpecConstant = Program::SpecializationConstant::Type; - char const* addr = (char*)specData + offset; - SpecConstant const& arg = specializationConstants[i].value; - if (std::holds_alternative(arg)) { - *((VkBool32*)addr) = std::get(arg) ? VK_TRUE : VK_FALSE; - } else if (std::holds_alternative(arg)) { - *((float*)addr) = std::get(arg); - } else { - *((int32_t*)addr) = std::get(arg); +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) + std::string name{ builder.getName().c_str(), builder.getName().size() }; + switch (static_cast(i)) { + case ShaderStage::VERTEX: + name += "_vs"; + break; + case ShaderStage::FRAGMENT: + name += "_fs"; + break; + default: + PANIC_POSTCONDITION("Unexpected stage"); + break; } + VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SHADER_MODULE, + reinterpret_cast(module), name.c_str()); +#endif } + LayoutDescriptionList& layouts = mInfo->layouts; + layouts[0].bindings = utils::FixedCapacityVector::with_capacity( + countBits(collapseStages(uboMask))); + layouts[1].bindings = utils::FixedCapacityVector::with_capacity( + countBits(collapseStages(samplerMask))); + layouts[2].bindings = utils::FixedCapacityVector::with_capacity( + countBits(collapseStages(inputAttachmentMask))); + + addDescriptors(uboMask, layouts[0].bindings); + addDescriptors(samplerMask, layouts[1].bindings); + addDescriptors(inputAttachmentMask, layouts[2].bindings); + +#if FVK_ENABLED_DEBUG_SAMPLER_NAME + auto& bindingToName = mInfo->bindingToName; +#endif + auto& groupInfo = builder.getSamplerGroupInfo(); auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; + auto& bindings = mInfo->bindings; auto& usage = mInfo->usage; for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) { auto const& group = groupInfo[groupInd]; @@ -110,32 +243,20 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept for (size_t i = 0; i < samplers.size(); ++i) { uint32_t const binding = samplers[i].binding; bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i); - usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage); + assert_invariant(bindings.find(binding) == bindings.end()); + bindings.insert(binding); + usage = getUsageFlags(binding, group.stageFlags, usage); + +#if FVK_ENABLED_DEBUG_SAMPLER_NAME + bindingToName[binding] = samplers[i].name.c_str(); +#endif } } - #if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) - utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex - << ", " << bundle.fragment << ")" << utils::io::endl; - #endif -} - -VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs, - CustomSamplerInfoList const& samplerInfo) noexcept - : VulkanResource(VulkanResourceType::PROGRAM), - mInfo(new PipelineInfo(0)), - mDevice(device) { - mInfo->shaders[0] = vs; - mInfo->shaders[1] = fs; - auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; - auto& usage = mInfo->usage; - bindingToSamplerIndex.resize(samplerInfo.size()); - for (uint16_t binding = 0; binding < samplerInfo.size(); ++binding) { - auto const& sampler = samplerInfo[binding]; - bindingToSamplerIndex[binding] - = (sampler.groupIndex << 8) | (0xff & sampler.samplerIndex); - usage = VulkanPipelineCache::getUsageFlags(binding, sampler.flags, usage); - } +#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) + utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << modules[0] + << ", " << modules[1] << ")" << utils::io::endl; +#endif } VulkanProgram::~VulkanProgram() { @@ -283,22 +404,21 @@ uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) co return count; } -VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, - VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount, - uint32_t elementCount, AttributeArray const& attribs) - : HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs), - VulkanResource(VulkanResourceType::VERTEX_BUFFER), - mInfo(new PipelineInfo(attribs.size())), - mResources(allocator) { - auto attribDesc = mInfo->mSoa.data(); - auto bufferDesc = mInfo->mSoa.data(); - auto offsets = mInfo->mSoa.data(); - auto attribToBufferIndex = mInfo->mSoa.data(); - std::fill(mInfo->mSoa.begin(), - mInfo->mSoa.end(), -1); - - for (uint32_t attribIndex = 0; attribIndex < attribs.size(); attribIndex++) { - Attribute attrib = attribs[attribIndex]; +VulkanVertexBufferInfo::VulkanVertexBufferInfo( + uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes) + : HwVertexBufferInfo(bufferCount, attributeCount), + VulkanResource(VulkanResourceType::VERTEX_BUFFER_INFO), + mInfo(attributes.size()) { + + auto attribDesc = mInfo.mSoa.data(); + auto bufferDesc = mInfo.mSoa.data(); + auto offsets = mInfo.mSoa.data(); + auto attribToBufferIndex = mInfo.mSoa.data(); + std::fill(mInfo.mSoa.begin(), + mInfo.mSoa.end(), -1); + + for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) { + Attribute attrib = attributes[attribIndex]; bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET; bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED; VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger); @@ -310,7 +430,7 @@ VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& // expects to receive floats or ints. if (attrib.buffer == Attribute::BUFFER_UNUSED) { vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM; - attrib = attribs[0]; + attrib = attributes[0]; } offsets[attribIndex] = attrib.offset; attribDesc[attribIndex] = { @@ -326,14 +446,23 @@ VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& } } -VulkanVertexBuffer::~VulkanVertexBuffer() { - delete mInfo; +VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, + VulkanResourceAllocator* allocator, + uint32_t vertexCount, Handle vbih) + : HwVertexBuffer(vertexCount), + VulkanResource(VulkanResourceType::VERTEX_BUFFER), + vbih(vbih), + mBuffers(MAX_VERTEX_BUFFER_COUNT), // TODO: can we do better here? + mResources(allocator) { } -void VulkanVertexBuffer::setBuffer(VulkanBufferObject* bufferObject, uint32_t index) { - size_t count = attributes.size(); - auto vkbuffers = mInfo->mSoa.data(); - auto attribToBuffer = mInfo->mSoa.data(); +void VulkanVertexBuffer::setBuffer(VulkanResourceAllocator const& allocator, + VulkanBufferObject* bufferObject, uint32_t index) { + VulkanVertexBufferInfo const* const vbi = + const_cast(allocator).handle_cast(vbih); + size_t const count = vbi->getAttributeCount(); + VkBuffer* const vkbuffers = getVkBuffers(); + int8_t const* const attribToBuffer = vbi->getAttributeToBuffer(); for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) { if (attribToBuffer[attribIndex] == static_cast(index)) { vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer(); @@ -349,35 +478,6 @@ VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount), bindingType(bindingType) {} -void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) { - this->type = pt; - switch (pt) { - case PrimitiveType::POINTS: - primitiveTopology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; - break; - case PrimitiveType::LINES: - primitiveTopology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; - break; - case PrimitiveType::LINE_STRIP: - primitiveTopology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; - break; - case PrimitiveType::TRIANGLES: - primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - break; - case PrimitiveType::TRIANGLE_STRIP: - primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; - break; - } -} - -void VulkanRenderPrimitive::setBuffers(VulkanVertexBuffer* vertexBuffer, - VulkanIndexBuffer* indexBuffer) { - this->vertexBuffer = vertexBuffer; - this->indexBuffer = indexBuffer; - mResources.acquire(vertexBuffer); - mResources.acquire(indexBuffer); -} - VulkanTimerQuery::VulkanTimerQuery(std::tuple indices) : VulkanThreadSafeResource(VulkanResourceType::TIMER_QUERY), mStartingQueryIndex(std::get<0>(indices)), @@ -409,4 +509,46 @@ bool VulkanTimerQuery::isCompleted() noexcept { VulkanTimerQuery::~VulkanTimerQuery() = default; +VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator, + PrimitiveType pt, Handle vbh, Handle ibh) + : VulkanResource(VulkanResourceType::RENDER_PRIMITIVE), + mResources(resourceAllocator) { + type = pt; + vertexBuffer = resourceAllocator->handle_cast(vbh); + indexBuffer = resourceAllocator->handle_cast(ibh); + mResources.acquire(vertexBuffer); + mResources.acquire(indexBuffer); +} + +using Bitmask = VulkanDescriptorSetLayout::Bitmask; + +Bitmask Bitmask::fromBackendLayout(descset::DescriptorSetLayout const& layout) { + Bitmask mask; + for (auto const& binding: layout.bindings) { + switch (binding.type) { + case descset::DescriptorType::UNIFORM_BUFFER: { + if (binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET) { + mask.dynamicUbo |= fromStageFlags(binding.stageFlags, + binding.binding); + } else { + mask.ubo |= fromStageFlags(binding.stageFlags, + binding.binding); + } + break; + } + case descset::DescriptorType::SAMPLER: { + mask.sampler |= fromStageFlags(binding.stageFlags, binding.binding); + break; + } + case descset::DescriptorType::INPUT_ATTACHMENT: { + mask.inputAttachment |= + fromStageFlags(binding.stageFlags, binding.binding); + break; + } + } + } + return mask; +} + + } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index f60d73ca550..8abdccfd968 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -21,36 +21,171 @@ #include "DriverBase.h" #include "VulkanBuffer.h" -#include "VulkanPipelineCache.h" #include "VulkanResources.h" #include "VulkanSwapChain.h" #include "VulkanTexture.h" #include "VulkanUtility.h" #include "private/backend/SamplerGroup.h" +#include "utils/FixedCapacityVector.h" +#include "vulkan/vulkan_core.h" #include #include namespace filament::backend { +using namespace descset; + class VulkanTimestamps; -struct VulkanProgram : public HwProgram, VulkanResource { +struct VulkanDescriptorSetLayout : public VulkanResource { + static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3; + + // The bitmask representation of a set layout. + struct Bitmask { + UniformBufferBitmask ubo = 0; // 4 bytes + UniformBufferBitmask dynamicUbo = 0; // 4 bytes + SamplerBitmask sampler = 0; // 8 bytes + InputAttachmentBitmask inputAttachment = 0; // 1 bytes + + // Because we're using this struct as hash key, must make it's 8-bytes aligned, with no + // unaccounted bytes. + uint8_t padding0 = 0; // 1 bytes + uint16_t padding1 = 0;// 2 bytes + uint32_t padding2 = 0;// 4 bytes + + bool operator==(Bitmask const& right) const { + return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler && + inputAttachment == right.inputAttachment; + } + + static Bitmask fromBackendLayout(descset::DescriptorSetLayout const& layout); + }; + + // This is a convenience struct to quickly check layout compatibility in terms of descriptor set + // pools. + struct Count { + uint32_t ubo = 0; + uint32_t dynamicUbo = 0; + uint32_t sampler = 0; + uint32_t inputAttachment = 0; + + bool operator==(Count const& right) const noexcept { + return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler && + inputAttachment == right.inputAttachment; + } + + static inline Count fromLayoutBitmask(Bitmask const& mask) { + return { + .ubo = countBits(collapseStages(mask.ubo)), + .dynamicUbo = countBits(collapseStages(mask.dynamicUbo)), + .sampler = countBits(collapseStages(mask.sampler)), + .inputAttachment = countBits(collapseStages(mask.inputAttachment)), + }; + } - VulkanProgram(VkDevice device, const Program& builder) noexcept; + Count operator*(uint16_t mult) const noexcept { + // TODO: check for overflow. - struct CustomSamplerInfo { - uint8_t groupIndex; - uint8_t samplerIndex; - ShaderStageFlags flags; + Count ret; + ret.ubo = ubo * mult; + ret.dynamicUbo = dynamicUbo * mult; + ret.sampler = sampler * mult; + ret.inputAttachment = inputAttachment * mult; + return ret; + } }; - using CustomSamplerInfoList = utils::FixedCapacityVector; - // We allow custom descriptor of the samplers within shaders. This is needed if we want to use - // a program that exists only in the backend - for example, for shader-based bliting. - VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs, - CustomSamplerInfoList const& samplerInfo) noexcept; + static_assert(sizeof(Bitmask) % 8 == 0); + + explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info, + Bitmask const& bitmask); + + ~VulkanDescriptorSetLayout(); + + VkDevice const mDevice; + VkDescriptorSetLayout const vklayout; + Bitmask const bitmask; + + // This is a convenience struct so that we don't have to iterate through all the bits of the + // bitmask (which correspondings to binding indices). + struct _Bindings { + utils::FixedCapacityVector const ubo; + utils::FixedCapacityVector const dynamicUbo; + utils::FixedCapacityVector const sampler; + utils::FixedCapacityVector const inputAttachment; + } bindings; + + Count const count; + +private: + + template + utils::FixedCapacityVector bits(MaskType mask) { + utils::FixedCapacityVector ret = + utils::FixedCapacityVector::with_capacity(countBits(mask)); + for (uint8_t i = 0; i < sizeof(mask) * 4; ++i) { + if (mask & (1 << i)) { + ret.push_back(i); + } + } + return ret; + } + + _Bindings getBindings(Bitmask const& bitmask) { + auto const uboCollapsed = collapseStages(bitmask.ubo); + auto const dynamicUboCollapsed = collapseStages(bitmask.dynamicUbo); + auto const samplerCollapsed = collapseStages(bitmask.sampler); + auto const inputAttachmentCollapsed = collapseStages(bitmask.inputAttachment); + return { + bits(uboCollapsed), + bits(dynamicUboCollapsed), + bits(samplerCollapsed), + bits(inputAttachmentCollapsed), + }; + } +}; + +using VulkanDescriptorSetLayoutList = std::array, + VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; + +struct VulkanDescriptorSet : public VulkanResource { +public: + // Because we need to recycle descriptor sets not used, we allow for a callback that the "Pool" + // can use to repackage the vk handle. + using OnRecycle = std::function; + + VulkanDescriptorSet(VulkanResourceAllocator* allocator, + VkDescriptorSet rawSet, OnRecycle&& onRecycleFn) + : VulkanResource(VulkanResourceType::DESCRIPTOR_SET), + resources(allocator), + vkSet(rawSet), + mOnRecycleFn(std::move(onRecycleFn)) {} + + ~VulkanDescriptorSet() { + if (mOnRecycleFn) { + mOnRecycleFn(); + } + } + + // TODO: maybe change to fixed size for performance. + VulkanAcquireOnlyResourceManager resources; + VkDescriptorSet const vkSet; + +private: + OnRecycle mOnRecycleFn; +}; + +using VulkanDescriptorSetList = std::array, + VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; + +struct VulkanProgram : public HwProgram, VulkanResource { + + using BindingList = CappedArray; + + VulkanProgram(VkDevice device, Program const& builder) noexcept; + ~VulkanProgram(); inline VkShaderModule getVertexShader() const { @@ -59,38 +194,66 @@ struct VulkanProgram : public HwProgram, VulkanResource { inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; } - inline VulkanPipelineCache::UsageFlags getUsage() const { return mInfo->usage; } - inline utils::FixedCapacityVector const& getBindingToSamplerIndex() const { return mInfo->bindingToSamplerIndex; } - inline VkSpecializationInfo const& getSpecConstInfo() const { - return mInfo->specializationInfo; + inline UsageFlags getUsage() const { + return mInfo->usage; } -private: + // Get a list of the sampler binding indices so that we don't have to loop through all possible + // samplers. + inline BindingList const& getBindings() const { return mInfo->bindings; } + + // TODO: this is currently not used. This will replace getLayoutDescriptionList below. + // inline descset::DescriptorSetLayout const& getLayoutDescription() const { + // return mInfo->layout; + // } + // In the usual case, we would have just one layout per program. But in the current setup, we + // have a set/layout for each descriptor type. This will be changed in the future. + using LayoutDescriptionList = std::array; + inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; } + +#if FVK_ENABLED_DEBUG_SAMPLER_NAME + inline utils::FixedCapacityVector const& getBindingToName() const { + return mInfo->bindingToName; + } +#endif + // TODO: handle compute shaders. // The expected order of shaders - from frontend to backend - is vertex, fragment, compute. - static constexpr uint8_t MAX_SHADER_MODULES = 2; + static constexpr uint8_t const MAX_SHADER_MODULES = 2; +private: struct PipelineInfo { - PipelineInfo(size_t specConstsCount) : - bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff), - specConsts(specConstsCount, VkSpecializationMapEntry{}), - specConstData(new char[specConstsCount * 4]) - {} + PipelineInfo() + : bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) +#if FVK_ENABLED_DEBUG_SAMPLER_NAME + , bindingToName(MAX_SAMPLER_COUNT, "") +#endif + {} // This bitset maps to each of the sampler in the sampler groups associated with this // program, and whether each sampler is used in which shader (i.e. vert, frag, compute). - VulkanPipelineCache::UsageFlags usage; + UsageFlags usage; + + BindingList bindings; // We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit. utils::FixedCapacityVector bindingToSamplerIndex; - VkShaderModule shaders[MAX_SHADER_MODULES] = {VK_NULL_HANDLE}; - VkSpecializationInfo specializationInfo = {}; - utils::FixedCapacityVector specConsts; - std::unique_ptr specConstData; + VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE }; + + // TODO: Use this instead of `layouts` after Filament-side Descriptor Set API is in place. + // descset::DescriptorSetLayout layout; + LayoutDescriptionList layouts; + +#if FVK_ENABLED_DEBUG_SAMPLER_NAME + // We store the sampler name mapped from binding index (only for debug purposes). + utils::FixedCapacityVector bindingToName; +#endif + }; PipelineInfo* mInfo; @@ -140,55 +303,72 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource { struct VulkanBufferObject; -struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource { - VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, - VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount, - uint32_t elementCount, AttributeArray const& attributes); - - ~VulkanVertexBuffer(); - - void setBuffer(VulkanBufferObject* bufferObject, uint32_t index); +struct VulkanVertexBufferInfo : public HwVertexBufferInfo, VulkanResource { + VulkanVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount, + AttributeArray const& attributes); - inline VkVertexInputAttributeDescription const* getAttribDescriptions() { - return mInfo->mSoa.data(); + inline VkVertexInputAttributeDescription const* getAttribDescriptions() const { + return mInfo.mSoa.data(); } - inline VkVertexInputBindingDescription const* getBufferDescriptions() { - return mInfo->mSoa.data(); + inline VkVertexInputBindingDescription const* getBufferDescriptions() const { + return mInfo.mSoa.data(); } - inline VkBuffer const* getVkBuffers() const { - return mInfo->mSoa.data(); + inline int8_t const* getAttributeToBuffer() const { + return mInfo.mSoa.data(); } inline VkDeviceSize const* getOffsets() const { - return mInfo->mSoa.data(); + return mInfo.mSoa.data(); + } + + size_t getAttributeCount() const noexcept { + return mInfo.mSoa.size(); } private: struct PipelineInfo { - PipelineInfo(size_t size) - : mSoa(size /* capacity */) { + PipelineInfo(size_t size) : mSoa(size /* capacity */) { mSoa.resize(size); } - // These corresponds to the index of the element in the SoA + // These correspond to the index of the element in the SoA static constexpr uint8_t ATTRIBUTE_DESCRIPTION = 0; static constexpr uint8_t BUFFER_DESCRIPTION = 1; - static constexpr uint8_t VK_BUFFER = 2; - static constexpr uint8_t OFFSETS = 3; - static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 4; + static constexpr uint8_t OFFSETS = 2; + static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 3; utils::StructureOfArrays< VkVertexInputAttributeDescription, VkVertexInputBindingDescription, - VkBuffer, VkDeviceSize, int8_t > mSoa; }; - PipelineInfo* mInfo; + PipelineInfo mInfo; +}; + +struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource { + VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, + VulkanResourceAllocator* allocator, + uint32_t vertexCount, Handle vbih); + + void setBuffer(VulkanResourceAllocator const& allocator, + VulkanBufferObject* bufferObject, uint32_t index); + + inline VkBuffer const* getVkBuffers() const { + return mBuffers.data(); + } + + inline VkBuffer* getVkBuffers() { + return mBuffers.data(); + } + + Handle vbih; +private: + utils::FixedCapacityVector mBuffers; FixedSizeVulkanResourceManager mResources; }; @@ -221,19 +401,15 @@ struct VulkanSamplerGroup : public HwSamplerGroup, VulkanResource { }; struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource { - VulkanRenderPrimitive(VulkanResourceAllocator* allocator) - : VulkanResource(VulkanResourceType::RENDER_PRIMITIVE), - mResources(allocator) {} + VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator, + PrimitiveType pt, Handle vbh, Handle ibh); ~VulkanRenderPrimitive() { mResources.clear(); } - void setPrimitiveType(PrimitiveType pt); - void setBuffers(VulkanVertexBuffer* vertexBuffer, VulkanIndexBuffer* indexBuffer); VulkanVertexBuffer* vertexBuffer = nullptr; VulkanIndexBuffer* indexBuffer = nullptr; - VkPrimitiveTopology primitiveTopology; private: // Keep references to the vertex buffer and the index buffer. diff --git a/filament/backend/src/vulkan/VulkanMemory.h b/filament/backend/src/vulkan/VulkanMemory.h index 13c09a6bfc8..a5ab2953192 100644 --- a/filament/backend/src/vulkan/VulkanMemory.h +++ b/filament/backend/src/vulkan/VulkanMemory.h @@ -31,4 +31,8 @@ #include "vk_mem_alloc.h" +VK_DEFINE_HANDLE(VmaAllocator) +VK_DEFINE_HANDLE(VmaAllocation) +VK_DEFINE_HANDLE(VmaPool) + #endif // TNT_FILAMENT_BACKEND_VULKANMEMORY_H diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index 000f8c755c3..7cfc74162e0 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -14,8 +14,9 @@ * limitations under the License. */ -#include "vulkan/VulkanMemory.h" -#include "vulkan/VulkanPipelineCache.h" +#include "VulkanPipelineCache.h" +#include "VulkanMemory.h" +#include "caching/VulkanDescriptorSetManager.h" #include #include @@ -34,66 +35,9 @@ using namespace bluevk; namespace filament::backend { -static VulkanPipelineCache::RasterState createDefaultRasterState(); - -static VkShaderStageFlags getShaderStageFlags(VulkanPipelineCache::UsageFlags key, uint16_t binding) { - // NOTE: if you modify this function, you also need to modify getUsageFlags. - assert_invariant(binding < MAX_SAMPLER_COUNT); - VkShaderStageFlags flags = 0; - if (key.test(binding)) { - flags |= VK_SHADER_STAGE_VERTEX_BIT; - } - if (key.test(MAX_SAMPLER_COUNT + binding)) { - flags |= VK_SHADER_STAGE_FRAGMENT_BIT; - } - return flags; -} - -VulkanPipelineCache::UsageFlags -VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) { - // NOTE: if you modify this function, you also need to modify getShaderStageFlags. - assert_invariant(binding < MAX_SAMPLER_COUNT); - if (any(flags & ShaderStageFlags::VERTEX)) { - src.set(binding); - } - if (any(flags & ShaderStageFlags::FRAGMENT)) { - src.set(MAX_SAMPLER_COUNT + binding); - } - // TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags - // has 186 bits (MAX_SAMPLER_COUNT * 3) - // assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT))); - return src; -} - -VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding, - UsageFlags src) { - src.unset(binding); - src.unset(MAX_SAMPLER_COUNT + binding); - return src; -} - -VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator) - : mCurrentRasterState(createDefaultRasterState()), - mResourceAllocator(allocator), - mPipelineBoundResources(allocator) { - mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - mDummyBufferWriteInfo.pNext = nullptr; - mDummyBufferWriteInfo.dstArrayElement = 0; - mDummyBufferWriteInfo.descriptorCount = 1; - mDummyBufferWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - mDummyBufferWriteInfo.pImageInfo = nullptr; - mDummyBufferWriteInfo.pBufferInfo = &mDummyBufferInfo; - mDummyBufferWriteInfo.pTexelBufferView = nullptr; - - mDummyTargetInfo.imageLayout = VulkanImageUtility::getVkLayout(VulkanLayout::READ_ONLY); - mDummyTargetWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - mDummyTargetWriteInfo.pNext = nullptr; - mDummyTargetWriteInfo.dstArrayElement = 0; - mDummyTargetWriteInfo.descriptorCount = 1; - mDummyTargetWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; - mDummyTargetWriteInfo.pImageInfo = &mDummyTargetInfo; - mDummyTargetWriteInfo.pBufferInfo = nullptr; - mDummyTargetWriteInfo.pTexelBufferView = nullptr; +VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VmaAllocator allocator) + : mDevice(device), + mAllocator(allocator) { } VulkanPipelineCache::~VulkanPipelineCache() { @@ -101,244 +45,47 @@ VulkanPipelineCache::~VulkanPipelineCache() { // be explicit about teardown order of various components. } -void VulkanPipelineCache::setDevice(VkDevice device, VmaAllocator allocator) { - assert_invariant(mDevice == VK_NULL_HANDLE); - mDevice = device; - mAllocator = allocator; - mDescriptorPool = createDescriptorPool(mDescriptorPoolSize); - - // Formulate some dummy objects and dummy descriptor info used only for clearing out unused - // bindings. This is especially crucial after a texture has been destroyed. Since core Vulkan - // does not allow specifying VK_NULL_HANDLE without the robustness2 extension, we would need to - // change the pipeline layout more frequently if we wanted to get rid of these dummy objects. - - VkBufferCreateInfo bufferInfo { - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .size = 16, - .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - }; - VmaAllocationCreateInfo allocInfo { .usage = VMA_MEMORY_USAGE_GPU_ONLY }; - vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo, &mDummyBuffer, &mDummyMemory, nullptr); - - mDummyBufferInfo.buffer = mDummyBuffer; - mDummyBufferInfo.range = bufferInfo.size; +void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept { + mPipelineRequirements.layout = layout; } -bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept { - DescriptorMap::iterator descriptorIter = mDescriptorSets.find(mDescriptorRequirements); - - // Check if the required descriptors are already bound. If so, there's no need to do anything. - if (DescEqual equals; UTILS_LIKELY(equals(mBoundDescriptor, mDescriptorRequirements))) { - - // If the pipeline state during an app's first draw call happens to match the default state - // vector of the cache, then the cache is uninitialized and we should not return early. - if (UTILS_LIKELY(!mDescriptorSets.empty())) { - - // Since the descriptors are already bound, they should be found in the cache. - assert_invariant(descriptorIter != mDescriptorSets.end()); - - // Update the LRU "time stamp" (really a count of cmd buf submissions) before returning. - descriptorIter.value().lastUsed = mCurrentTime; - return true; - } - } - +VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipeline() noexcept { // If a cached object exists, re-use it, otherwise create a new one. - DescriptorCacheEntry* cacheEntry = UTILS_LIKELY(descriptorIter != mDescriptorSets.end()) ? - &descriptorIter.value() : createDescriptorSets(); - - // If a descriptor set overflow occurred, allow higher levels to handle it gracefully. - assert_invariant(cacheEntry != nullptr); - if (UTILS_UNLIKELY(cacheEntry == nullptr)) { - return false; + if (PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements); + pipelineIter != mPipelines.end()) { + auto& pipeline = pipelineIter.value(); + pipeline.lastUsed = mCurrentTime; + return &pipeline; } - - cacheEntry->lastUsed = mCurrentTime; - mBoundDescriptor = mDescriptorRequirements; - // This passes the currently "bound" uniform buffer objects to pipeline that will be used in the - // draw call. - auto resourceEntry = mDescriptorResources.find(cacheEntry->id); - if (resourceEntry == mDescriptorResources.end()) { - mDescriptorResources[cacheEntry->id] - = std::make_unique(mResourceAllocator); - resourceEntry = mDescriptorResources.find(cacheEntry->id); - } - resourceEntry->second->acquireAll(&mPipelineBoundResources); - - vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT, - cacheEntry->handles.data(), 0, nullptr); - - return true; + auto ret = createPipeline(); + ret->lastUsed = mCurrentTime; + return ret; } -bool VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) noexcept { +void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) { VkCommandBuffer const cmdbuffer = commands->buffer(); - PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements); - + PipelineCacheEntry* cacheEntry = getOrCreatePipeline(); // Check if the required pipeline is already bound. - if (PipelineEqual equals; UTILS_LIKELY(equals(mBoundPipeline, mPipelineRequirements))) { - assert_invariant(pipelineIter != mPipelines.end()); - pipelineIter.value().lastUsed = mCurrentTime; - return true; + if (cacheEntry->handle == commands->pipeline()) { + return; } - // If a cached object exists, re-use it, otherwise create a new one. - PipelineCacheEntry* cacheEntry = UTILS_LIKELY(pipelineIter != mPipelines.end()) ? - &pipelineIter.value() : createPipeline(); - // If an error occurred, allow higher levels to handle it gracefully. - assert_invariant(cacheEntry != nullptr); - if (UTILS_UNLIKELY(cacheEntry == nullptr)) { - return false; - } - - cacheEntry->lastUsed = mCurrentTime; - getOrCreatePipelineLayout()->lastUsed = mCurrentTime; + assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline"); mBoundPipeline = mPipelineRequirements; - vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle); - return true; + commands->setPipeline(cacheEntry->handle); } void VulkanPipelineCache::bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept { - if (UTILS_UNLIKELY(!equivalent(mCurrentScissor, scissor))) { - mCurrentScissor = scissor; - vkCmdSetScissor(cmdbuffer, 0, 1, &scissor); - } -} - -VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptorSets() noexcept { - PipelineLayoutCacheEntry* layoutCacheEntry = getOrCreatePipelineLayout(); - - DescriptorCacheEntry descriptorCacheEntry = { - .pipelineLayout = mPipelineRequirements.layout, - .id = mDescriptorCacheEntryCount++, - }; - - // Each of the arenas for this particular layout are guaranteed to have the same size. Check - // the first arena to see if any descriptor sets are available that can be re-claimed. If not, - // create brand new ones (one for each type). They will be added to the arena later, after they - // are no longer used. This occurs during the cleanup phase during command buffer submission. - auto& descriptorSetArenas = layoutCacheEntry->descriptorSetArenas; - if (descriptorSetArenas[0].empty()) { - - // If allocating a new descriptor set from the pool would cause it to overflow, then - // recreate the pool. The number of descriptor sets that have already been allocated from - // the pool is the sum of the "active" descriptor sets (mDescriptorSets) and the "dormant" - // descriptor sets (mDescriptorArenasCount). - // - // NOTE: technically both sides of the inequality below should be multiplied by - // DESCRIPTOR_TYPE_COUNT to get the true number of descriptor sets. - if (mDescriptorSets.size() + mDescriptorArenasCount + 1 > mDescriptorPoolSize) { - growDescriptorPool(); - } - - VkDescriptorSetAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocInfo.descriptorPool = mDescriptorPool; - allocInfo.descriptorSetCount = DESCRIPTOR_TYPE_COUNT; - allocInfo.pSetLayouts = layoutCacheEntry->descriptorSetLayouts.data(); - VkResult error = vkAllocateDescriptorSets(mDevice, &allocInfo, - descriptorCacheEntry.handles.data()); - assert_invariant(error == VK_SUCCESS); - if (error != VK_SUCCESS) { - return nullptr; - } - } else { - for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) { - descriptorCacheEntry.handles[i] = descriptorSetArenas[i].back(); - descriptorSetArenas[i].pop_back(); - } - assert_invariant(mDescriptorArenasCount > 0); - mDescriptorArenasCount--; - } - - // Rewrite every binding in the new descriptor sets. - VkDescriptorBufferInfo descriptorBuffers[UBUFFER_BINDING_COUNT]; - VkDescriptorImageInfo descriptorSamplers[SAMPLER_BINDING_COUNT]; - VkDescriptorImageInfo descriptorInputAttachments[INPUT_ATTACHMENT_COUNT]; - VkWriteDescriptorSet descriptorWrites[UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT + - INPUT_ATTACHMENT_COUNT]; - uint32_t nwrites = 0; - VkWriteDescriptorSet* writes = descriptorWrites; - nwrites = 0; - for (uint32_t binding = 0; binding < UBUFFER_BINDING_COUNT; binding++) { - VkWriteDescriptorSet& writeInfo = writes[nwrites++]; - if (mDescriptorRequirements.uniformBuffers[binding]) { - VkDescriptorBufferInfo& bufferInfo = descriptorBuffers[binding]; - bufferInfo.buffer = mDescriptorRequirements.uniformBuffers[binding]; - bufferInfo.offset = mDescriptorRequirements.uniformBufferOffsets[binding]; - bufferInfo.range = mDescriptorRequirements.uniformBufferSizes[binding]; - - // We store size with 32 bits, so our "WHOLE" sentinel is different from Vk. - if (bufferInfo.range == WHOLE_SIZE) { - bufferInfo.range = VK_WHOLE_SIZE; - } - - writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writeInfo.pNext = nullptr; - writeInfo.dstArrayElement = 0; - writeInfo.descriptorCount = 1; - writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - writeInfo.pImageInfo = nullptr; - writeInfo.pBufferInfo = &bufferInfo; - writeInfo.pTexelBufferView = nullptr; - } else { - writeInfo = mDummyBufferWriteInfo; - assert_invariant(mDummyBufferWriteInfo.pBufferInfo->buffer); - } - assert_invariant(writeInfo.pBufferInfo->buffer); - writeInfo.dstSet = descriptorCacheEntry.handles[0]; - writeInfo.dstBinding = binding; - } - for (uint32_t binding = 0; binding < SAMPLER_BINDING_COUNT; binding++) { - if (mDescriptorRequirements.samplers[binding].sampler) { - VkWriteDescriptorSet& writeInfo = writes[nwrites++]; - VkDescriptorImageInfo& imageInfo = descriptorSamplers[binding]; - imageInfo = mDescriptorRequirements.samplers[binding]; - writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writeInfo.pNext = nullptr; - writeInfo.dstArrayElement = 0; - writeInfo.descriptorCount = 1; - writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - writeInfo.pImageInfo = &imageInfo; - writeInfo.pBufferInfo = nullptr; - writeInfo.pTexelBufferView = nullptr; - writeInfo.dstSet = descriptorCacheEntry.handles[1]; - writeInfo.dstBinding = binding; - } - } - for (uint32_t binding = 0; binding < INPUT_ATTACHMENT_COUNT; binding++) { - if (mDescriptorRequirements.inputAttachments[binding].imageView) { - VkWriteDescriptorSet& writeInfo = writes[nwrites++]; - VkDescriptorImageInfo& imageInfo = descriptorInputAttachments[binding]; - imageInfo = mDescriptorRequirements.inputAttachments[binding]; - writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writeInfo.pNext = nullptr; - writeInfo.dstArrayElement = 0; - writeInfo.descriptorCount = 1; - writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; - writeInfo.pImageInfo = &imageInfo; - writeInfo.pBufferInfo = nullptr; - writeInfo.pTexelBufferView = nullptr; - writeInfo.dstSet = descriptorCacheEntry.handles[2]; - writeInfo.dstBinding = binding; - } - } - - vkUpdateDescriptorSets(mDevice, nwrites, writes, 0, nullptr); - - return &mDescriptorSets.emplace(mDescriptorRequirements, descriptorCacheEntry).first.value(); + vkCmdSetScissor(cmdbuffer, 0, 1, &scissor); } VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept { assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound."); - - PipelineLayoutCacheEntry* layout = getOrCreatePipelineLayout(); - assert_invariant(layout); + assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified"); VkPipelineShaderStageCreateInfo shaderStages[SHADER_MODULE_COUNT]; shaderStages[0] = VkPipelineShaderStageCreateInfo{}; @@ -360,9 +107,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n // If we reach this point, we need to create and stash a brand new pipeline object. shaderStages[0].module = mPipelineRequirements.shaders[0]; - shaderStages[0].pSpecializationInfo = mSpecializationRequirements; shaderStages[1].module = mPipelineRequirements.shaders[1]; - shaderStages[1].pSpecializationInfo = mSpecializationRequirements; // Expand our size-optimized structs into the proper Vk structs. uint32_t numVertexAttribs = 0; @@ -409,7 +154,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineCreateInfo.layout = layout->handle; + pipelineCreateInfo.layout = mPipelineRequirements.layout; pipelineCreateInfo.renderPass = mPipelineRequirements.renderPass; pipelineCreateInfo.subpass = mPipelineRequirements.subpassIndex; pipelineCreateInfo.stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1; @@ -503,76 +248,21 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value(); } -VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreatePipelineLayout() noexcept { - auto iter = mPipelineLayouts.find(mPipelineRequirements.layout); - if (UTILS_LIKELY(iter != mPipelineLayouts.end())) { - return &iter.value(); - } - - PipelineLayoutCacheEntry cacheEntry = {}; - - VkDescriptorSetLayoutBinding binding = {}; - binding.descriptorCount = 1; // NOTE: We never use arrays-of-blocks. - binding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; // NOTE: This is potentially non-optimal. - - // First create the descriptor set layout for UBO's. - VkDescriptorSetLayoutBinding ubindings[UBUFFER_BINDING_COUNT]; - binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) { - binding.binding = i; - ubindings[i] = binding; - } - VkDescriptorSetLayoutCreateInfo dlinfo = {}; - dlinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - dlinfo.bindingCount = UBUFFER_BINDING_COUNT; - dlinfo.pBindings = ubindings; - vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[0]); - - // Next create the descriptor set layout for samplers. - VkDescriptorSetLayoutBinding sbindings[SAMPLER_BINDING_COUNT]; - binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) { - binding.stageFlags = getShaderStageFlags(mPipelineRequirements.layout, i); - binding.binding = i; - sbindings[i] = binding; - } - dlinfo.bindingCount = SAMPLER_BINDING_COUNT; - dlinfo.pBindings = sbindings; - vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[1]); - - // Next create the descriptor set layout for input attachments. - VkDescriptorSetLayoutBinding tbindings[INPUT_ATTACHMENT_COUNT]; - binding.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; - binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) { - binding.binding = i; - tbindings[i] = binding; - } - dlinfo.bindingCount = INPUT_ATTACHMENT_COUNT; - dlinfo.pBindings = tbindings; - vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[2]); - - // Create VkPipelineLayout based on how to resources are bounded. - VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {}; - pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pPipelineLayoutCreateInfo.setLayoutCount = cacheEntry.descriptorSetLayouts.size(); - pPipelineLayoutCreateInfo.pSetLayouts = cacheEntry.descriptorSetLayouts.data(); - VkResult result = vkCreatePipelineLayout(mDevice, &pPipelineLayoutCreateInfo, VKALLOC, - &cacheEntry.handle); - if (UTILS_UNLIKELY(result != VK_SUCCESS)) { - return nullptr; - } - return &mPipelineLayouts.emplace(mPipelineRequirements.layout, cacheEntry).first.value(); -} - void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept { mPipelineRequirements.shaders[0] = program->getVertexShader(); mPipelineRequirements.shaders[1] = program->getFragmentShader(); - mSpecializationRequirements = &program->getSpecConstInfo(); + + // If this is a debug build, validate the current shader. +#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) + if (mPipelineRequirements.shaders[0] == VK_NULL_HANDLE || + mPipelineRequirements.shaders[1] == VK_NULL_HANDLE) { + utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl; + } +#endif } void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept { - mPipelineRequirements.rasterState = mCurrentRasterState = rasterState; + mPipelineRequirements.rasterState = rasterState; } void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept { @@ -598,99 +288,15 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons } } -VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding( - uint32_t bindingIndex) const noexcept { - auto& key = mDescriptorRequirements; - return { - key.uniformBuffers[bindingIndex], - key.uniformBufferOffsets[bindingIndex], - key.uniformBufferSizes[bindingIndex], - }; -} - -void VulkanPipelineCache::unbindUniformBuffer(VkBuffer uniformBuffer) noexcept { - auto& key = mDescriptorRequirements; - for (uint32_t bindingIndex = 0u; bindingIndex < UBUFFER_BINDING_COUNT; ++bindingIndex) { - if (key.uniformBuffers[bindingIndex] == uniformBuffer) { - key.uniformBuffers[bindingIndex] = {}; - key.uniformBufferSizes[bindingIndex] = {}; - key.uniformBufferOffsets[bindingIndex] = {}; - } - } -} - -void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept { - for (auto& sampler : mDescriptorRequirements.samplers) { - if (sampler.imageView == imageView) { - sampler = {}; - } - } - for (auto& target : mDescriptorRequirements.inputAttachments) { - if (target.imageView == imageView) { - target = {}; - } - } -} - -void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex, - VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { - bindUniformBuffer(bindingIndex, bufferObject->buffer.getGpuBuffer(), offset, size); - mPipelineBoundResources.acquire(bufferObject); -} - -void VulkanPipelineCache::bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer, - VkDeviceSize offset, VkDeviceSize size) noexcept { - ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT, - "Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex, - UBUFFER_BINDING_COUNT); - auto& key = mDescriptorRequirements; - key.uniformBuffers[bindingIndex] = buffer; - - if (size == VK_WHOLE_SIZE) { - size = WHOLE_SIZE; - } - - assert_invariant(offset <= 0xffffffffu); - assert_invariant(size <= 0xffffffffu); - - key.uniformBufferOffsets[bindingIndex] = offset; - key.uniformBufferSizes[bindingIndex] = size; -} - -void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT], - VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept { - for (uint32_t bindingIndex = 0; bindingIndex < SAMPLER_BINDING_COUNT; bindingIndex++) { - mDescriptorRequirements.samplers[bindingIndex] = samplers[bindingIndex]; - if (textures[bindingIndex]) { - mPipelineBoundResources.acquire(textures[bindingIndex]); - } - } - mPipelineRequirements.layout = flags; -} - -void VulkanPipelineCache::bindInputAttachment(uint32_t bindingIndex, - VkDescriptorImageInfo targetInfo) noexcept { - ASSERT_POSTCONDITION(bindingIndex < INPUT_ATTACHMENT_COUNT, - "Input attachment bindings overflow: index = %d, capacity = %d.", - bindingIndex, INPUT_ATTACHMENT_COUNT); - mDescriptorRequirements.inputAttachments[bindingIndex] = targetInfo; -} - void VulkanPipelineCache::terminate() noexcept { - // Symmetric to createLayoutsAndDescriptors. - destroyLayoutsAndDescriptors(); for (auto& iter : mPipelines) { vkDestroyPipeline(mDevice, iter.second.handle, VKALLOC); } - mPipelineBoundResources.clear(); mPipelines.clear(); mBoundPipeline = {}; - vmaDestroyBuffer(mAllocator, mDummyBuffer, mDummyMemory); - mDummyBuffer = VK_NULL_HANDLE; - mDummyMemory = VK_NULL_HANDLE; } -void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) { +void VulkanPipelineCache::gc() noexcept { // The timestamp associated with a given cache entry represents "time" as a count of flush // events since the cache was constructed. If any cache entry was most recently used over // FVK_MAX_PIPELINE_AGE flush events in the past, then we can be sure that it is no longer @@ -700,194 +306,22 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) { // The Vulkan spec says: "When a command buffer begins recording, all state in that command // buffer is undefined." Therefore, we need to clear all bindings at this time. mBoundPipeline = {}; - mBoundDescriptor = {}; mCurrentScissor = {}; // NOTE: Due to robin_map restrictions, we cannot use auto or range-based loops. - // Check if any bundles in the cache are no longer in use by any command buffer. Descriptors - // from unused bundles are moved back to their respective arenas. - using ConstDescIterator = decltype(mDescriptorSets)::const_iterator; - for (ConstDescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end();) { - const DescriptorCacheEntry& cacheEntry = iter.value(); - if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) { - auto& arenas = mPipelineLayouts[cacheEntry.pipelineLayout].descriptorSetArenas; - for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) { - arenas[i].push_back(cacheEntry.handles[i]); - } - ++mDescriptorArenasCount; - mDescriptorResources.erase(cacheEntry.id); - iter = mDescriptorSets.erase(iter); - } else { - ++iter; - } - } - // Evict any pipelines that have not been used in a while. // Any pipeline older than FVK_MAX_COMMAND_BUFFERS can be safely destroyed. - using ConstPipeIterator = decltype(mPipelines)::const_iterator; - for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) { - const PipelineCacheEntry& cacheEntry = iter.value(); - if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) { - vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC); - iter = mPipelines.erase(iter); - } else { - ++iter; - } - } - - // Evict any layouts that have not been used in a while. - using ConstLayoutIterator = decltype(mPipelineLayouts)::const_iterator; - for (ConstLayoutIterator iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end();) { - const PipelineLayoutCacheEntry& cacheEntry = iter.value(); - if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) { - vkDestroyPipelineLayout(mDevice, iter->second.handle, VKALLOC); - for (auto setLayout : iter->second.descriptorSetLayouts) { - #if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE) - PipelineLayoutKey key = iter.key(); - for (auto& pair : mDescriptorSets) { - assert_invariant(pair.second.pipelineLayout != key); - } - #endif - vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC); - } - auto& arenas = iter->second.descriptorSetArenas; - assert_invariant(mDescriptorArenasCount >= arenas[0].size()); - mDescriptorArenasCount -= arenas[0].size(); - for (auto& arena : arenas) { - vkFreeDescriptorSets(mDevice, mDescriptorPool, arena.size(), arena.data()); - } - iter = mPipelineLayouts.erase(iter); - } else { - ++iter; - } - } - - // If there are no descriptors from any extinct pool that are still in use, we can safely - // destroy the extinct pools, which implicitly frees their associated descriptor sets. - bool canPurgeExtinctPools = true; - for (auto& bundle : mExtinctDescriptorBundles) { - if (bundle.lastUsed + FVK_MAX_PIPELINE_AGE >= mCurrentTime) { - canPurgeExtinctPools = false; - break; - } - } - if (canPurgeExtinctPools) { - for (VkDescriptorPool pool : mExtinctDescriptorPools) { - vkDestroyDescriptorPool(mDevice, pool, VKALLOC); - } - mExtinctDescriptorPools.clear(); - - for (auto const& entry : mExtinctDescriptorBundles) { - mDescriptorResources.erase(entry.id); - } - mExtinctDescriptorBundles.clear(); - } -} - -VkDescriptorPool VulkanPipelineCache::createDescriptorPool(uint32_t size) const { - VkDescriptorPoolSize poolSizes[DESCRIPTOR_TYPE_COUNT] = {}; - VkDescriptorPoolCreateInfo poolInfo { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - .maxSets = size * DESCRIPTOR_TYPE_COUNT, - .poolSizeCount = DESCRIPTOR_TYPE_COUNT, - .pPoolSizes = poolSizes - }; - poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSizes[0].descriptorCount = poolInfo.maxSets * UBUFFER_BINDING_COUNT; - poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = poolInfo.maxSets * SAMPLER_BINDING_COUNT; - poolSizes[2].type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; - poolSizes[2].descriptorCount = poolInfo.maxSets * INPUT_ATTACHMENT_COUNT; - - VkDescriptorPool pool; - const UTILS_UNUSED VkResult result = vkCreateDescriptorPool(mDevice, &poolInfo, VKALLOC, &pool); - assert_invariant(result == VK_SUCCESS); - return pool; -} - -void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept { - // Our current descriptor set strategy can cause the # of descriptor sets to explode in certain - // situations, so it's interesting to report the number that get stuffed into the cache. - #if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE) - utils::slog.d << "Destroying " << mDescriptorSets.size() << " bundles of descriptor sets." - << utils::io::endl; - #endif - - mDescriptorSets.clear(); - - // Our current layout bundle strategy can cause the # of layout bundles to explode in certain - // situations, so it's interesting to report the number that get stuffed into the cache. - #if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE) - utils::slog.d << "Destroying " << mPipelineLayouts.size() << " pipeline layouts." - << utils::io::endl; - #endif - - for (auto& iter : mPipelineLayouts) { - vkDestroyPipelineLayout(mDevice, iter.second.handle, VKALLOC); - for (auto setLayout : iter.second.descriptorSetLayouts) { - vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC); - } - // There is no need to free descriptor sets individually since destroying the VkDescriptorPool - // implicitly frees them. - } - mPipelineLayouts.clear(); - vkDestroyDescriptorPool(mDevice, mDescriptorPool, VKALLOC); - mDescriptorPool = VK_NULL_HANDLE; - - for (VkDescriptorPool pool : mExtinctDescriptorPools) { - vkDestroyDescriptorPool(mDevice, pool, VKALLOC); - } - mExtinctDescriptorPools.clear(); - mExtinctDescriptorBundles.clear(); - - // Both mDescriptorSets and mExtinctDescriptorBundles have been cleared, so it's safe to call - // clear() on mDescriptorResources. - mDescriptorResources.clear(); - - mBoundDescriptor = {}; -} - -void VulkanPipelineCache::growDescriptorPool() noexcept { - // We need to destroy the old VkDescriptorPool, but we can't do so immediately because many - // of its descriptors are still in use. So, stash it in an "extinct" list. - mExtinctDescriptorPools.push_back(mDescriptorPool); - - // Create the new VkDescriptorPool, twice as big as the old one. - mDescriptorPoolSize *= 2; - mDescriptorPool = createDescriptorPool(mDescriptorPoolSize); - - // Clear out all unused descriptor sets in the arena so they don't get reclaimed. There is no - // need to free them individually since the old VkDescriptorPool will be destroyed. - for (auto iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end(); ++iter) { - for (auto& arena : iter.value().descriptorSetArenas) { - arena.clear(); - } - } - mDescriptorArenasCount = 0; - - // Move all in-use descriptors from the primary cache into an "extinct" list, so that they will - // later be destroyed rather than reclaimed. - using DescIterator = decltype(mDescriptorSets)::iterator; - for (DescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end(); ++iter) { - mExtinctDescriptorBundles.push_back(iter.value()); - } - mDescriptorSets.clear(); -} - -size_t VulkanPipelineCache::PipelineLayoutKeyHashFn::operator()( - const PipelineLayoutKey& key) const { - std::hash hasher; - auto h0 = hasher(key.getBitsAt(0)); - auto h1 = hasher(key.getBitsAt(1)); - return h0 ^ (h1 << 1); -} - -bool VulkanPipelineCache::PipelineLayoutKeyEqual::operator()(const PipelineLayoutKey& k1, - const PipelineLayoutKey& k2) const { - return k1 == k2; + using ConstPipeIterator = decltype(mPipelines)::const_iterator; + for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) { + const PipelineCacheEntry& cacheEntry = iter.value(); + if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) { + vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC); + iter = mPipelines.erase(iter); + } else { + ++iter; + } + } } bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1, @@ -895,48 +329,6 @@ bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1, return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(k1)); } -bool VulkanPipelineCache::DescEqual::operator()(const DescriptorKey& k1, - const DescriptorKey& k2) const { - for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) { - if (k1.uniformBuffers[i] != k2.uniformBuffers[i] || - k1.uniformBufferOffsets[i] != k2.uniformBufferOffsets[i] || - k1.uniformBufferSizes[i] != k2.uniformBufferSizes[i]) { - return false; - } - } - for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) { - if (k1.samplers[i].sampler != k2.samplers[i].sampler || - k1.samplers[i].imageView != k2.samplers[i].imageView || - k1.samplers[i].imageLayout != k2.samplers[i].imageLayout) { - return false; - } - } - for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) { - if (k1.inputAttachments[i].imageView != k2.inputAttachments[i].imageView || - k1.inputAttachments[i].imageLayout != k2.inputAttachments[i].imageLayout) { - return false; - } - } - return true; -} - -static VulkanPipelineCache::RasterState createDefaultRasterState() { - return VulkanPipelineCache::RasterState { - .cullMode = VK_CULL_MODE_NONE, - .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, - .depthBiasEnable = VK_FALSE, - .blendEnable = VK_FALSE, - .depthWriteEnable = VK_TRUE, - .alphaToCoverageEnable = true, - .colorWriteMask = 0xf, - .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, - .colorTargetCount = 1, - .depthCompareOp = SamplerCompareFunc::LE, - .depthBiasConstantFactor = 0.0f, - .depthBiasSlopeFactor = 0.0f, - }; -} - } // namespace filament::backend #pragma clang diagnostic pop diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.h b/filament/backend/src/vulkan/VulkanPipelineCache.h index 0e092e9555f..53eaf71287c 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.h +++ b/filament/backend/src/vulkan/VulkanPipelineCache.h @@ -17,6 +17,11 @@ #ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H #define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H +#include "VulkanCommands.h" +#include "VulkanMemory.h" +#include "VulkanResources.h" +#include "VulkanUtility.h" + #include #include @@ -34,12 +39,6 @@ #include #include -#include "VulkanCommands.h" - -VK_DEFINE_HANDLE(VmaAllocator) -VK_DEFINE_HANDLE(VmaAllocation) -VK_DEFINE_HANDLE(VmaPool) - namespace filament::backend { struct VulkanProgram; @@ -56,32 +55,14 @@ class VulkanResourceAllocator; // - Assumes that viewport and scissor should be dynamic. (not baked into VkPipeline) // - Assumes that uniform buffers should be visible across all shader stages. // -class VulkanPipelineCache : public CommandBufferObserver { +class VulkanPipelineCache { public: VulkanPipelineCache(VulkanPipelineCache const&) = delete; VulkanPipelineCache& operator=(VulkanPipelineCache const&) = delete; - static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::UNIFORM_BINDING_COUNT; - static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT; - - // We assume only one possible input attachment between two subpasses. See also the subpasses - // definition in VulkanFboCache. - static constexpr uint32_t INPUT_ATTACHMENT_COUNT = 1; - static constexpr uint32_t SHADER_MODULE_COUNT = 2; static constexpr uint32_t VERTEX_ATTRIBUTE_COUNT = MAX_VERTEX_ATTRIBUTE_COUNT; - // Three descriptor set layouts: uniforms, combined image samplers, and input attachments. - static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 3; - static constexpr uint32_t INITIAL_DESCRIPTOR_SET_POOL_SIZE = 512; - - // The VertexArray POD is an array of buffer targets and an array of attributes that refer to - // those targets. It does not include any references to actual buffers, so you can think of it - // as a vertex assembler configuration. For simplicity it contains fixed-size arrays and does - // not store sizes; all unused entries are simply zeroed out. - struct VertexArray { - }; - // The ProgramBundle contains weak references to the compiled vertex and fragment shaders. struct ProgramBundle { VkShaderModule vertex; @@ -89,10 +70,6 @@ class VulkanPipelineCache : public CommandBufferObserver { VkSpecializationInfo* specializationInfos = nullptr; }; - using UsageFlags = utils::bitset128; - static UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags stages, UsageFlags src = {}); - static UsageFlags disableUsageFlags(uint16_t binding, UsageFlags src); - #pragma clang diagnostic push #pragma clang diagnostic warning "-Wpadded" @@ -135,17 +112,13 @@ class VulkanPipelineCache : public CommandBufferObserver { // Upon construction, the pipeCache initializes some internal state but does not make any Vulkan // calls. On destruction it will free any cached Vulkan objects that haven't already been freed. - VulkanPipelineCache(VulkanResourceAllocator* allocator); + VulkanPipelineCache(VkDevice device, VmaAllocator allocator); ~VulkanPipelineCache(); - void setDevice(VkDevice device, VmaAllocator allocator); - // Creates new descriptor sets if necessary and binds them using vkCmdBindDescriptorSets. - // Returns false if descriptor set allocation fails. - bool bindDescriptors(VkCommandBuffer cmdbuffer) noexcept; + void bindLayout(VkPipelineLayout layout) noexcept; // Creates a new pipeline if necessary and binds it using vkCmdBindPipeline. - // Returns false if an error occurred. - bool bindPipeline(VulkanCommandBuffer* commands) noexcept; + void bindPipeline(VulkanCommandBuffer* commands); // Sets up a new scissor rectangle if it has been dirtied. void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept; @@ -155,75 +128,31 @@ class VulkanPipelineCache : public CommandBufferObserver { void bindRasterState(const RasterState& rasterState) noexcept; void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept; void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept; - void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject, - VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept; - void bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer, - VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept; - void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT], - VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept; - void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept; + void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc, VkVertexInputBindingDescription const* bufferDesc, uint8_t count); - // Gets the current UBO at the given slot, useful for push / pop. - UniformBufferBinding getUniformBufferBinding(uint32_t bindingIndex) const noexcept; - - // Checks if the given uniform is bound to any slot, and if so binds "null" to that slot. - // Also invalidates all cached descriptors that refer to the given buffer. - // This is only necessary when the client knows that the UBO is about to be destroyed. - void unbindUniformBuffer(VkBuffer uniformBuffer) noexcept; - - // Checks if an image view is bound to any sampler, and if so resets that particular slot. - // Also invalidates all cached descriptors that refer to the given image view. - // This is only necessary when the client knows that a texture is about to be destroyed. - void unbindImageView(VkImageView imageView) noexcept; - - // NOTE: In theory we should proffer "unbindSampler" but in practice we never destroy samplers. - // Destroys all managed Vulkan objects. This should be called before changing the VkDevice. void terminate() noexcept; - // vkCmdBindPipeline and vkCmdBindDescriptorSets establish bindings to a specific command - // buffer; they are not global to the device. Therefore we need to be notified when a - // new command buffer becomes active. - void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) override; - - // Injects a dummy texture that can be used to clear out old descriptor sets. - void setDummyTexture(VkImageView imageView) { - mDummyTargetInfo.imageView = imageView; - } - - // Acquires a resource to be bound to the current pipeline. The ownership of the resource - // will be transferred to the corresponding pipeline when pipeline is bound. - void acquireResource(VulkanResource* resource) { - mPipelineBoundResources.acquire(resource); - } - - inline RasterState getCurrentRasterState() const noexcept { - return mCurrentRasterState; + static VkPrimitiveTopology getPrimitiveTopology(PrimitiveType pt) noexcept { + switch (pt) { + case PrimitiveType::POINTS: + return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case PrimitiveType::LINES: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case PrimitiveType::LINE_STRIP: + return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + case PrimitiveType::TRIANGLES: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case PrimitiveType::TRIANGLE_STRIP: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + } } - // We need to update this outside of bindRasterState due to VulkanDriver::draw. - inline void setCurrentRasterState(RasterState const& rasterState) noexcept { - mCurrentRasterState = rasterState; - } + void gc() noexcept; private: - // PIPELINE LAYOUT CACHE KEY - // ------------------------- - - using PipelineLayoutKey = utils::bitset128; - - static_assert(PipelineLayoutKey::BIT_COUNT >= 2 * MAX_SAMPLER_COUNT); - - struct PipelineLayoutKeyHashFn { - size_t operator()(const PipelineLayoutKey& key) const; - }; - - struct PipelineLayoutKeyEqual { - bool operator()(const PipelineLayoutKey& k1, const PipelineLayoutKey& k2) const; - }; - // PIPELINE CACHE KEY // ------------------ @@ -276,10 +205,10 @@ class VulkanPipelineCache : public CommandBufferObserver { VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156 RasterState rasterState; // 16 : 284 uint32_t padding; // 4 : 300 - PipelineLayoutKey layout; // 16 : 304 + VkPipelineLayout layout; // 8 : 304 }; - static_assert(sizeof(PipelineKey) == 320, "PipelineKey must not have implicit padding."); + static_assert(sizeof(PipelineKey) == 312, "PipelineKey must not have implicit padding."); using PipelineHashFn = utils::hash::MurmurHashFn; @@ -287,52 +216,6 @@ class VulkanPipelineCache : public CommandBufferObserver { bool operator()(const PipelineKey& k1, const PipelineKey& k2) const; }; - // DESCRIPTOR SET CACHE KEY - // ------------------------ - - // Equivalent to VkDescriptorImageInfo but with explicit padding. - struct DescriptorImageInfo { - DescriptorImageInfo& operator=(const VkDescriptorImageInfo& that) { - sampler = that.sampler; - imageView = that.imageView; - imageLayout = that.imageLayout; - padding = 0; - return *this; - } - operator VkDescriptorImageInfo() const { return { sampler, imageView, imageLayout }; } - - // TODO: replace the 64-bit sampler handle with `uint32_t samplerParams` and remove the - // padding field. This is possible if we have access to the VulkanSamplerCache. - VkSampler sampler; - - VkImageView imageView; - VkImageLayout imageLayout; - uint32_t padding; - }; - - // We store size with 32 bits, so our "WHOLE" sentinel is different from Vk. - static const uint32_t WHOLE_SIZE = 0xffffffffu; - - // Represents all the Vulkan state that comprises a bound descriptor set. - struct DescriptorKey { - VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 80 0 - DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 1488 80 - DescriptorImageInfo inputAttachments[INPUT_ATTACHMENT_COUNT]; // 24 1568 - uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 40 1592 - uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 40 1632 - }; - static_assert(offsetof(DescriptorKey, samplers) == 80); - static_assert(offsetof(DescriptorKey, inputAttachments) == 1568); - static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1592); - static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1632); - static_assert(sizeof(DescriptorKey) == 1672, "DescriptorKey must not have implicit padding."); - - using DescHashFn = utils::hash::MurmurHashFn; - - struct DescEqual { - bool operator()(const DescriptorKey& k1, const DescriptorKey& k2) const; - }; - #pragma clang diagnostic pop // CACHE ENTRY STRUCTS @@ -345,16 +228,6 @@ class VulkanPipelineCache : public CommandBufferObserver { using Timestamp = uint64_t; Timestamp mCurrentTime = 0; - // The descriptor set cache entry is a group of descriptor sets that are bound simultaneously. - struct DescriptorCacheEntry { - std::array handles; - Timestamp lastUsed; - PipelineLayoutKey pipelineLayout; - uint32_t id; - }; - uint32_t mDescriptorCacheEntryCount = 0; - - struct PipelineCacheEntry { VkPipeline handle; Timestamp lastUsed; @@ -363,100 +236,36 @@ class VulkanPipelineCache : public CommandBufferObserver { struct PipelineLayoutCacheEntry { VkPipelineLayout handle; Timestamp lastUsed; - - std::array descriptorSetLayouts; - - // Each pipeline layout has 3 arenas of unused descriptors (one for each binding type). - // - // The difference between the "arenas" and the "pool" are as follows. - // - // - The "pool" is a single, centralized factory for all descriptors (VkDescriptorPool). - // - // - Each "arena" is a set of unused (but alive) descriptors that can only be used with a - // specific pipeline layout and a specific binding type. We manually manage each arena. - // The arenas are created in an empty state, and they are gradually populated as new - // descriptors are reclaimed over time. This is quite different from the pool, which is - // given a fixed size when it is constructed. - // - std::array, DESCRIPTOR_TYPE_COUNT> descriptorSetArenas; }; // CACHE CONTAINERS // ---------------- - using PipelineLayoutMap = tsl::robin_map; using PipelineMap = tsl::robin_map; - using DescriptorMap - = tsl::robin_map; - using DescriptorResourceMap - = std::unordered_map>; - PipelineLayoutMap mPipelineLayouts; +private: + + PipelineCacheEntry* getOrCreatePipeline() noexcept; + PipelineMap mPipelines; - DescriptorMap mDescriptorSets; - DescriptorResourceMap mDescriptorResources; // These helpers all return unstable pointers that should not be stored. - DescriptorCacheEntry* createDescriptorSets() noexcept; PipelineCacheEntry* createPipeline() noexcept; PipelineLayoutCacheEntry* getOrCreatePipelineLayout() noexcept; - // Misc helper methods. - void destroyLayoutsAndDescriptors() noexcept; - VkDescriptorPool createDescriptorPool(uint32_t size) const; - void growDescriptorPool() noexcept; - // Immutable state. VkDevice mDevice = VK_NULL_HANDLE; VmaAllocator mAllocator = VK_NULL_HANDLE; // Current requirements for the pipeline layout, pipeline, and descriptor sets. - RasterState mCurrentRasterState; PipelineKey mPipelineRequirements = {}; - DescriptorKey mDescriptorRequirements = {}; - VkSpecializationInfo const* mSpecializationRequirements = nullptr; // Current bindings for the pipeline and descriptor sets. PipelineKey mBoundPipeline = {}; - DescriptorKey mBoundDescriptor = {}; // Current state for scissoring. VkRect2D mCurrentScissor = {}; - - // The descriptor set pool starts out with a decent number of descriptor sets. The cache can - // grow the pool by re-creating it with a larger size. See growDescriptorPool(). - VkDescriptorPool mDescriptorPool; - - // This describes the number of descriptor sets in mDescriptorPool. Note that this needs to be - // multiplied by DESCRIPTOR_TYPE_COUNT to get the actual number of descriptor sets. Also note - // that the number of low-level "descriptors" (not descriptor *sets*) is actually much more than - // this size. It can be computed only by factoring in UBUFFER_BINDING_COUNT etc. - uint32_t mDescriptorPoolSize = INITIAL_DESCRIPTOR_SET_POOL_SIZE; - - // To get the actual number of descriptor sets that have been allocated from the pool, - // take the sum of mDescriptorArenasCount (these are inactive descriptor sets) and the - // number of entries in the mDescriptorPool map (active descriptor sets). Multiply the result by - // DESCRIPTOR_TYPE_COUNT. - uint32_t mDescriptorArenasCount = 0; - - // After a growth event (i.e. when the VkDescriptorPool is replaced with a bigger version), all - // currently used descriptors are moved into the "extinct" sets so that they can be safely - // destroyed a few frames later. - std::list mExtinctDescriptorPools; - std::list mExtinctDescriptorBundles; - - VkDescriptorBufferInfo mDummyBufferInfo = {}; - VkWriteDescriptorSet mDummyBufferWriteInfo = {}; - VkDescriptorImageInfo mDummyTargetInfo = {}; - VkWriteDescriptorSet mDummyTargetWriteInfo = {}; - - VkBuffer mDummyBuffer; - VmaAllocation mDummyMemory; - - VulkanResourceAllocator* mResourceAllocator; - VulkanAcquireOnlyResourceManager mPipelineBoundResources; }; } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanResourceAllocator.h b/filament/backend/src/vulkan/VulkanResourceAllocator.h index 222c7be9360..3afc0d6b965 100644 --- a/filament/backend/src/vulkan/VulkanResourceAllocator.h +++ b/filament/backend/src/vulkan/VulkanResourceAllocator.h @@ -17,6 +17,7 @@ #ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H #define TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H +#include "VulkanConstants.h" #include "VulkanHandles.h" #include @@ -29,18 +30,17 @@ namespace filament::backend { -// RESOURCE_TYPE_COUNT matches the count of enum VulkanResourceType. -#define RESOURCE_TYPE_COUNT 12 -#define DEBUG_RESOURCE_LEAKS 0 +#define RESOURCE_TYPE_COUNT (static_cast(VulkanResourceType::END_TYPE)) +#define DEBUG_RESOURCE_LEAKS FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK) #if DEBUG_RESOURCE_LEAKS - #define TRACK_INCREMENT() \ - if (!IS_MANAGED_TYPE(obj->mType)) { \ - mDebugOnlyResourceCount[static_cast(obj->mType)]++; \ + #define TRACK_INCREMENT() \ + if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \ + mDebugOnlyResourceCount[static_cast(obj->getType())]++; \ } - #define TRACK_DECREMENT() \ - if (!IS_MANAGED_TYPE(obj->mType)) { \ - mDebugOnlyResourceCount[static_cast(obj->mType)]--; \ + #define TRACK_DECREMENT() \ + if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \ + mDebugOnlyResourceCount[static_cast(obj->getType())]--; \ } #else // No-op @@ -49,10 +49,10 @@ namespace filament::backend { #endif class VulkanResourceAllocator { - public: - VulkanResourceAllocator(size_t arenaSize) - : mHandleAllocatorImpl("Handles", arenaSize) + using AllocatorImpl = HandleAllocatorVK; + 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); @@ -106,7 +106,7 @@ class VulkanResourceAllocator { } private: - HandleAllocatorVK mHandleAllocatorImpl; + AllocatorImpl mHandleAllocatorImpl; #if DEBUG_RESOURCE_LEAKS public: diff --git a/filament/backend/src/vulkan/VulkanResources.cpp b/filament/backend/src/vulkan/VulkanResources.cpp index 5eef8e0aac0..deb3a3168e6 100644 --- a/filament/backend/src/vulkan/VulkanResources.cpp +++ b/filament/backend/src/vulkan/VulkanResources.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ - #include "VulkanResources.h" #include "VulkanHandles.h" #include "VulkanResourceAllocator.h" +#include "VulkanPipelineCache.h" namespace filament::backend { @@ -53,16 +53,27 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t case VulkanResourceType::TIMER_QUERY: allocator->destruct(Handle(id)); break; + case VulkanResourceType::VERTEX_BUFFER_INFO: + allocator->destruct(Handle(id)); + break; case VulkanResourceType::VERTEX_BUFFER: allocator->destruct(Handle(id)); break; case VulkanResourceType::RENDER_PRIMITIVE: allocator->destruct(Handle(id)); break; + case VulkanResourceType::DESCRIPTOR_SET_LAYOUT: + allocator->destruct(Handle(id)); + break; + case VulkanResourceType::DESCRIPTOR_SET: + allocator->destruct(Handle(id)); + break; + // If the resource is heap allocated, then the resource manager just skip refcounted // destruction. case VulkanResourceType::FENCE: case VulkanResourceType::HEAP_ALLOCATED: + case VulkanResourceType::END_TYPE: break; } } diff --git a/filament/backend/src/vulkan/VulkanResources.h b/filament/backend/src/vulkan/VulkanResources.h index 77b6498b860..cbfb70ab608 100644 --- a/filament/backend/src/vulkan/VulkanResources.h +++ b/filament/backend/src/vulkan/VulkanResources.h @@ -17,6 +17,8 @@ #ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCES_H #define TNT_FILAMENT_BACKEND_VULKANRESOURCES_H +#include "VulkanUtility.h" + #include #include @@ -33,20 +35,25 @@ struct VulkanThreadSafeResource; // Subclasses of VulkanResource must provide this enum in their construction. enum class VulkanResourceType : uint8_t { - BUFFER_OBJECT, - INDEX_BUFFER, - PROGRAM, - RENDER_TARGET, - SAMPLER_GROUP, - SWAP_CHAIN, - RENDER_PRIMITIVE, - TEXTURE, - TIMER_QUERY, - VERTEX_BUFFER, + BUFFER_OBJECT = 0, + INDEX_BUFFER = 1, + PROGRAM = 2, + RENDER_TARGET = 3, + SAMPLER_GROUP = 4, + SWAP_CHAIN = 5, + RENDER_PRIMITIVE = 6, + TEXTURE = 7, + TIMER_QUERY = 8, + VERTEX_BUFFER = 9, + VERTEX_BUFFER_INFO = 10, + DESCRIPTOR_SET_LAYOUT = 11, + DESCRIPTOR_SET = 12, // Below are resources that are managed manually (i.e. not ref counted). - FENCE, - HEAP_ALLOCATED, + FENCE = 13, + HEAP_ALLOCATED = 14, + + END_TYPE = 15, // A placeholder }; #define IS_HEAP_ALLOC_TYPE(f) \ @@ -62,15 +69,16 @@ struct VulkanResourceBase { protected: explicit VulkanResourceBase(VulkanResourceType type) : mRefCount(IS_HEAP_ALLOC_TYPE(type) ? 1 : 0), - mType(type), - mHandleId(0) {} + mType(uint32_t(type)), + mHandleId(0) { + } private: - inline VulkanResourceType getType() { - return mType; + inline VulkanResourceType getType() const noexcept { + return VulkanResourceType(mType); } - inline HandleBase::HandleId getId() { + inline HandleBase::HandleId getId() const noexcept { return mHandleId; } @@ -79,25 +87,27 @@ struct VulkanResourceBase { } inline void ref() noexcept { - if (IS_HEAP_ALLOC_TYPE(mType)) { + if (IS_HEAP_ALLOC_TYPE(getType())) { return; } + assert_invariant(mRefCount < ((1<<24) - 1)); ++mRefCount; } inline void deref() noexcept { - if (IS_HEAP_ALLOC_TYPE(mType)) { + if (IS_HEAP_ALLOC_TYPE(getType())) { return; } + assert_invariant(mRefCount > 0); --mRefCount; } - inline size_t refcount() noexcept { + inline size_t refcount() const noexcept { return mRefCount; } - size_t mRefCount = 0; - VulkanResourceType mType = VulkanResourceType::BUFFER_OBJECT; + uint32_t mRefCount : 24; // 16M is enough for the refcount + uint32_t mType : 8; // must be uint32_t or MSVC doesn't pack it. no codegen impact w/ clang. HandleBase::HandleId mHandleId; friend struct VulkanThreadSafeResource; @@ -107,6 +117,8 @@ struct VulkanResourceBase { friend class VulkanResourceManagerImpl; }; +static_assert(sizeof(VulkanResourceBase) == 8, "VulkanResourceBase should be 8 bytes"); + struct VulkanThreadSafeResource { protected: explicit VulkanThreadSafeResource(VulkanResourceType type) @@ -156,62 +168,7 @@ namespace { // When the size of the resource set is known to be small, (for example for VulkanRenderPrimitive), // we just use a std::array to back the set. template -class FixedCapacityResourceSet { -private: - using FixedSizeArray = std::array; - -public: - using const_iterator = typename FixedSizeArray::const_iterator; - - inline ~FixedCapacityResourceSet() { - clear(); - } - - inline const_iterator begin() { - if (mInd == 0) { - return mArray.cend(); - } - return mArray.cbegin(); - } - - inline const_iterator end() { - if (mInd == 0) { - return mArray.cend(); - } - if (mInd < SIZE) { - return mArray.begin() + mInd; - } - return mArray.cend(); - } - - inline const_iterator find(VulkanResource* resource) { - return std::find(begin(), end(), resource); - } - - inline void insert(VulkanResource* resource) { - assert_invariant(mInd < SIZE); - mArray[mInd++] = resource; - } - - inline void erase(VulkanResource* resource) { - assert_invariant(false && "FixedCapacityResourceSet::erase should not be called"); - } - - inline void clear() { - if (mInd == 0) { - return; - } - mInd = 0; - } - - inline size_t size() { - return mInd; - } - -private: - FixedSizeArray mArray{nullptr}; - size_t mInd = 0; -}; +using FixedCapacityResourceSet = CappedArray; // robin_set/map are useful for sets that are acquire only and the set will be iterated when the set // is cleared. diff --git a/filament/backend/src/vulkan/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..d2aacae5013 100644 --- a/filament/backend/src/vulkan/VulkanStagePool.h +++ b/filament/backend/src/vulkan/VulkanStagePool.h @@ -17,7 +17,9 @@ #ifndef TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H #define TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H +#include "VulkanCommands.h" #include "VulkanContext.h" +#include "VulkanMemory.h" #include #include @@ -45,7 +47,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/src/vulkan/VulkanSwapChain.cpp b/filament/backend/src/vulkan/VulkanSwapChain.cpp index e85844f951a..108cceca5c2 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.cpp +++ b/filament/backend/src/vulkan/VulkanSwapChain.cpp @@ -35,7 +35,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& mStagePool(stagePool), mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow), mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize), - mImageReady(VK_NULL_HANDLE), + mCurrentImageReadyIndex(0), mAcquired(false), mIsFirstRenderPass(true) { swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent); @@ -46,10 +46,15 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& }; // No need to wait on this semaphore before drawing when in Headless mode. - if (!mHeadless) { - VkResult result = - vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, &mImageReady); - ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore"); + if (mHeadless) { + // Set all sempahores to VK_NULL_HANDLE + memset(mImageReady, 0, sizeof(mImageReady[0]) * IMAGE_READY_SEMAPHORE_COUNT); + } else { + for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) { + VkResult result = + vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, mImageReady + i); + ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore"); + } } update(); @@ -62,9 +67,11 @@ VulkanSwapChain::~VulkanSwapChain() { mCommands->wait(); mPlatform->destroy(swapChain); - if (mImageReady != VK_NULL_HANDLE) { - vkDestroySemaphore(mPlatform->getDevice(), mImageReady, VKALLOC); - } + for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) { + if (mImageReady[i] != VK_NULL_HANDLE) { + vkDestroySemaphore(mPlatform->getDevice(), mImageReady[i], VKALLOC); + } + } } void VulkanSwapChain::update() { @@ -99,11 +106,16 @@ void VulkanSwapChain::present() { mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT); } mCommands->flush(); - VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal(); - VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing); - ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR - || result == VK_ERROR_OUT_OF_DATE_KHR, - "Cannot present in swapchain."); + + // We only present if it is not headless. No-op for headless (but note that we still need the + // flush() in the above line). + if (!mHeadless) { + VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal(); + VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing); + ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR || + result == VK_ERROR_OUT_OF_DATE_KHR, + "Cannot present in swapchain."); + } // We presented the last acquired buffer. mAcquired = false; @@ -126,11 +138,13 @@ void VulkanSwapChain::acquire(bool& resized) { update(); } - VkResult const result = mPlatform->acquire(swapChain, mImageReady, &mCurrentSwapIndex); + mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT; + const VkSemaphore imageReady = mImageReady[mCurrentImageReadyIndex]; + VkResult const result = mPlatform->acquire(swapChain, imageReady, &mCurrentSwapIndex); ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR, "Cannot acquire in swapchain."); - if (mImageReady != VK_NULL_HANDLE) { - mCommands->injectDependency(mImageReady); + if (imageReady != VK_NULL_HANDLE) { + mCommands->injectDependency(imageReady); } mAcquired = true; } diff --git a/filament/backend/src/vulkan/VulkanSwapChain.h b/filament/backend/src/vulkan/VulkanSwapChain.h index 345dd2650d3..55be65d4035 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.h +++ b/filament/backend/src/vulkan/VulkanSwapChain.h @@ -19,6 +19,7 @@ #include "DriverBase.h" +#include "VulkanCommands.h" #include "VulkanContext.h" #include "VulkanResources.h" @@ -69,6 +70,8 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource { } private: + static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS; + void update(); VulkanPlatform* mPlatform; @@ -83,7 +86,8 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource { utils::FixedCapacityVector> mColors; std::unique_ptr mDepth; VkExtent2D mExtent; - VkSemaphore mImageReady; + VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT]; + uint32_t mCurrentImageReadyIndex; uint32_t mCurrentSwapIndex; bool mAcquired; bool mIsFirstRenderPass; diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp index ec5c3fc93cf..f611f40aac7 100644 --- a/filament/backend/src/vulkan/VulkanTexture.cpp +++ b/filament/backend/src/vulkan/VulkanTexture.cpp @@ -18,10 +18,10 @@ #include "VulkanTexture.h" #include "VulkanUtility.h" +#include +#include #include -#include "DataReshaper.h" - #include using namespace bluevk; @@ -100,9 +100,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, // Filament expects blit() to work with any texture, so we almost always set these usage flags. // TODO: investigate performance implications of setting these flags. - const VkImageUsageFlags blittable = VK_IMAGE_USAGE_TRANSFER_DST_BIT | + constexpr VkImageUsageFlags blittable = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::BLIT_SRC))) { + imageInfo.usage |= blittable; + } + if (any(usage & TextureUsage::SAMPLEABLE)) { #if FVK_ENABLED(FVK_DEBUG_TEXTURE) @@ -159,7 +163,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, imageInfo.samples = (VkSampleCountFlagBits) samples; VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage); - if (error || FVK_ENABLED_BOOL(FVK_DEBUG_TEXTURE)) { + if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) { utils::slog.d << "vkCreateImage: " << "image = " << mTextureImage << ", " << "result = " << error << ", " @@ -272,6 +276,8 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt return; } + assert_invariant(hostData->size > 0 && "Data is empty"); + // Otherwise, use vkCmdCopyBufferToImage. void* mapped = nullptr; VulkanStage const* stage = mStagePool.acquireStage(hostData->size); diff --git a/filament/backend/src/vulkan/VulkanTexture.h b/filament/backend/src/vulkan/VulkanTexture.h index d6a36ecdc52..7b0b64a8c4c 100644 --- a/filament/backend/src/vulkan/VulkanTexture.h +++ b/filament/backend/src/vulkan/VulkanTexture.h @@ -23,6 +23,7 @@ #include "VulkanResources.h" #include "VulkanImageUtility.h" +#include #include #include diff --git a/filament/backend/src/vulkan/VulkanUtility.cpp b/filament/backend/src/vulkan/VulkanUtility.cpp index 612acb20e5d..4b60b92f4d8 100644 --- a/filament/backend/src/vulkan/VulkanUtility.cpp +++ b/filament/backend/src/vulkan/VulkanUtility.cpp @@ -655,4 +655,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) { return mostSignificantBit((sampleCount - 1) & mask); } +_BitCountHelper _BitCountHelper::BitCounter = {}; + } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanUtility.h b/filament/backend/src/vulkan/VulkanUtility.h index 3ba35c75e69..a5e053633fa 100644 --- a/filament/backend/src/vulkan/VulkanUtility.h +++ b/filament/backend/src/vulkan/VulkanUtility.h @@ -23,6 +23,8 @@ #include +#include + namespace filament::backend { VkFormat getVkFormat(ElementType type, bool normalized, bool integer); @@ -88,10 +90,237 @@ utils::FixedCapacityVector enumerate( #undef EXPAND_ENUM_NO_ARGS #undef EXPAND_ENUM_ARGS +// Used across pipeline related classes. +using UsageFlags = utils::bitset128; // Useful shorthands using VkFormatList = utils::FixedCapacityVector; +// An Array that will be fixed capacity, but the "size" (as in user added elements) is variable. +// Note that this class is movable. +template +class CappedArray { +private: + using FixedSizeArray = std::array; +public: + using const_iterator = typename FixedSizeArray::const_iterator; + using iterator = typename FixedSizeArray::iterator; + + CappedArray() = default; + + // Delete copy constructor/assignment. + CappedArray(CappedArray const& rhs) = delete; + CappedArray& operator=(CappedArray& rhs) = delete; + + CappedArray(CappedArray&& rhs) noexcept { + this->swap(rhs); + } + + CappedArray& operator=(CappedArray&& rhs) noexcept { + this->swap(rhs); + return *this; + } + + inline ~CappedArray() { + clear(); + } + + inline const_iterator begin() const { + if (mInd == 0) { + return mArray.cend(); + } + return mArray.cbegin(); + } + + inline const_iterator end() const { + if (mInd > 0 && mInd < CAPACITY) { + return mArray.begin() + mInd; + } + return mArray.cend(); + } + + inline T back() { + assert_invariant(mInd > 0); + return *(mArray.begin() + mInd); + } + + inline void pop_back() { + assert_invariant(mInd > 0); + mInd--; + } + + inline const_iterator find(T item) { + return std::find(begin(), end(), item); + } + + inline void insert(T item) { + assert_invariant(mInd < CAPACITY); + mArray[mInd++] = item; + } + + inline void erase(T item) { + PANIC_PRECONDITION("CappedArray::erase should not be called"); + } + + inline void clear() { + if (mInd == 0) { + return; + } + mInd = 0; + } + + inline T& operator[](uint16_t ind) { + return mArray[ind]; + } + + inline T const& operator[](uint16_t ind) const { + return mArray[ind]; + } + + inline uint32_t size() const { + return mInd; + } + + T* data() { + return mArray.data(); + } + + T const* data() const { + return mArray.data(); + } + + bool operator==(CappedArray const& b) const { + return this->mArray == b.mArray; + } + +private: + void swap(CappedArray& rhs) { + std::swap(mArray, rhs.mArray); + std::swap(mInd, rhs.mInd); + } + + FixedSizeArray mArray; + uint32_t mInd = 0; +}; + +// TODO: ok to remove once Filament-side API is complete +namespace descset { + +// Used to describe the descriptor binding in shader stages. We assume that the binding index does +// not exceed 31. We also assume that we have two shader stages - vertex and fragment. The below +// types and struct are used across VulkanDescriptorSet and VulkanProgram. +using UniformBufferBitmask = uint32_t; +using SamplerBitmask = uint64_t; + +// We only have at most one input attachment, so this bitmask exists only to make the code more +// general. +using InputAttachmentBitmask = uint8_t; + +constexpr UniformBufferBitmask UBO_VERTEX_STAGE = 0x1; +constexpr UniformBufferBitmask UBO_FRAGMENT_STAGE = (0x1ULL << (sizeof(UniformBufferBitmask) * 4)); +constexpr SamplerBitmask SAMPLER_VERTEX_STAGE = 0x1; +constexpr SamplerBitmask SAMPLER_FRAGMENT_STAGE = (0x1ULL << (sizeof(SamplerBitmask) * 4)); +constexpr InputAttachmentBitmask INPUT_ATTACHMENT_VERTEX_STAGE = 0x1; +constexpr InputAttachmentBitmask INPUT_ATTACHMENT_FRAGMENT_STAGE = + (0x1ULL << (sizeof(InputAttachmentBitmask) * 4)); + +template +static constexpr Bitmask getVertexStage() noexcept { + if constexpr (std::is_same_v) { + return UBO_VERTEX_STAGE; + } + if constexpr (std::is_same_v) { + return SAMPLER_VERTEX_STAGE; + } + if constexpr (std::is_same_v) { + return INPUT_ATTACHMENT_VERTEX_STAGE; + } +} + +template +static constexpr Bitmask getFragmentStage() noexcept { + if constexpr (std::is_same_v) { + return UBO_FRAGMENT_STAGE; + } + if constexpr (std::is_same_v) { + return SAMPLER_FRAGMENT_STAGE; + } + if constexpr (std::is_same_v) { + return INPUT_ATTACHMENT_FRAGMENT_STAGE; + } +} + +typedef enum ShaderStageFlags2 : uint8_t { + NONE = 0, + VERTEX = 0x1, + FRAGMENT = 0x2, +} ShaderStageFlags2; + +enum class DescriptorType : uint8_t { + UNIFORM_BUFFER, + SAMPLER, + INPUT_ATTACHMENT, +}; + +enum class DescriptorFlags : uint8_t { + NONE = 0x00, + DYNAMIC_OFFSET = 0x01 +}; + +struct DescriptorSetLayoutBinding { + DescriptorType type; + ShaderStageFlags2 stageFlags; + uint8_t binding; + DescriptorFlags flags; + uint16_t count; +}; + +struct DescriptorSetLayout { + utils::FixedCapacityVector bindings; +}; + +} // namespace descset + +// Use constexpr to statically generate a bit count table for 8-bit numbers. +struct _BitCountHelper { + constexpr _BitCountHelper() : data{} { + for (uint16_t i = 0; i < 256; ++i) { + data[i] = 0; + for (auto j = i; j > 0; j /= 2) { + if (j & 1) { + data[i]++; + } + } + } + } + + template + constexpr uint8_t count(MaskType num) { + uint8_t count = 0; + for (uint8_t i = 0; i < sizeof(MaskType) * 8; i+=8) { + count += data[(num >> i) & 0xFF]; + } + return count; + } + + static _BitCountHelper BitCounter; +private: + uint8_t data[256]; +}; + +template +inline uint8_t countBits(MaskType num) { + return _BitCountHelper::BitCounter.count(num); +} + +// This is useful for counting the total number of descriptors for both vertex and fragment stages. +template +inline MaskType collapseStages(MaskType mask) { + constexpr uint8_t NBITS_DIV_2 = sizeof(MaskType) * 4; + // First zero out the top-half and then or the bottom-half against the original top-half. + return ((mask << NBITS_DIV_2) >> NBITS_DIV_2) | (mask >> NBITS_DIV_2); +} + } // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_VULKANUTILITY_H diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp new file mode 100644 index 00000000000..cad4339a1b0 --- /dev/null +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp @@ -0,0 +1,1206 @@ +/* + * 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 "VulkanDescriptorSetManager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace filament::backend { + +namespace { + +// This assumes we have at most 32-bound samplers, 10 UBOs and, 1 input attachment. +// TODO: Obsolete after [GDSR]. +constexpr uint8_t MAX_SAMPLER_BINDING = 32; +constexpr uint8_t MAX_UBO_BINDING = 10; +constexpr uint8_t MAX_INPUT_ATTACHMENT_BINDING = 1; +constexpr uint8_t MAX_BINDINGS = + MAX_SAMPLER_BINDING + MAX_UBO_BINDING + MAX_INPUT_ATTACHMENT_BINDING; + +using ImgUtil = VulkanImageUtility; + +using Bitmask = VulkanDescriptorSetLayout::Bitmask; +using DescriptorCount = VulkanDescriptorSetLayout::Count; +using UBOMap = std::array, MAX_UBO_BINDING>; +using SamplerMap = + std::array, MAX_SAMPLER_BINDING>; +using BitmaskHashFn = utils::hash::MurmurHashFn; +struct BitmaskEqual { + bool operator()(Bitmask const& k1, Bitmask const& k2) const { + return k1 == k2; + } +}; + +// We create a pool for each layout as defined by the number of descriptors of each type. For +// example, a layout of +// 'A' => +// layout(binding = 0, set = 1) uniform {}; +// layout(binding = 1, set = 1) sampler1; +// layout(binding = 2, set = 1) sampler2; +// +// would be equivalent to +// 'B' => +// layout(binding = 1, set = 2) uniform {}; +// layout(binding = 2, set = 2) sampler2; +// layout(binding = 3, set = 2) sampler3; +// +// TODO: we might do better if we understand the types of unique layouts and can combine them in a +// single pool without too much waste. +class DescriptorPool { +public: + DescriptorPool(VkDevice device, VulkanResourceAllocator* allocator, + DescriptorCount const& count, uint16_t capacity) + : mDevice(device), + mAllocator(allocator), + mCount(count), + mCapacity(capacity), + mSize(0), + mUnusedCount(0), + mDisableRecycling(false) { + DescriptorCount const actual = mCount * capacity; + VkDescriptorPoolSize sizes[4]; + uint8_t npools = 0; + if (actual.ubo) { + sizes[npools++] = { + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = actual.ubo, + }; + } + if (actual.dynamicUbo) { + sizes[npools++] = { + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, + .descriptorCount = actual.dynamicUbo, + }; + } + if (actual.sampler) { + sizes[npools++] = { + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = actual.sampler, + }; + } + if (actual.inputAttachment) { + sizes[npools++] = { + .type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + .descriptorCount = actual.inputAttachment, + }; + } + VkDescriptorPoolCreateInfo info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .maxSets = capacity, + .poolSizeCount = npools, + .pPoolSizes = sizes, + }; + vkCreateDescriptorPool(mDevice, &info, VKALLOC, &mPool); + } + + DescriptorPool(DescriptorPool const&) = delete; + DescriptorPool& operator=(DescriptorPool const&) = delete; + + ~DescriptorPool() { + // Note that these have to manually destroyed because they were not explicitly ref-counted. + for (auto const& [mask, sets]: mUnused) { + for (auto set: sets) { + mAllocator->destruct(set); + } + } + vkDestroyDescriptorPool(mDevice, mPool, VKALLOC); + } + + void disableRecycling() noexcept { + mDisableRecycling = true; + } + + uint16_t const& capacity() { + return mCapacity; + } + + // A convenience method for checking if this pool can allocate sets for a given layout. + inline bool canAllocate(VulkanDescriptorSetLayout* layout) { + return layout->count == mCount; + } + + Handle obtainSet(VulkanDescriptorSetLayout* layout) { + if (UnusedSetMap::iterator itr = mUnused.find(layout->bitmask); itr != mUnused.end()) { + // If we don't have any unused, then just return an empty handle. + if (itr->second.empty()) { + return {}; + } + std::vector>& sets = itr->second; + auto set = sets.back(); + sets.pop_back(); + mUnusedCount--; + return set; + } + if (mSize + 1 > mCapacity) { + return {}; + } + // Creating a new set + VkDescriptorSetLayout layouts[1] = {layout->vklayout}; + VkDescriptorSetAllocateInfo allocInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = nullptr, + .descriptorPool = mPool, + .descriptorSetCount = 1, + .pSetLayouts = layouts, + }; + VkDescriptorSet vkSet; + UTILS_UNUSED VkResult result = vkAllocateDescriptorSets(mDevice, &allocInfo, &vkSet); + ASSERT_POSTCONDITION(result == VK_SUCCESS, + "Failed to allocate descriptor set code=%d size=%d capacity=%d count=%s", result, + mSize, mCapacity); + mSize++; + return createSet(layout->bitmask, vkSet); + } + +private: + Handle createSet(Bitmask const& layoutMask, VkDescriptorSet vkSet) { + return mAllocator->initHandle(mAllocator, vkSet, + [this, layoutMask, vkSet]() { + if (mDisableRecycling) { + return; + } + // We are recycling - release the set back into the pool. Note that the + // vk handle has not changed, but we need to change the backend handle to allow + // for proper refcounting of resources referenced in this set. + auto setHandle = createSet(layoutMask, vkSet); + if (auto itr = mUnused.find(layoutMask); itr != mUnused.end()) { + itr->second.push_back(setHandle); + } else { + mUnused[layoutMask].push_back(setHandle); + } + mUnusedCount++; + }); + } + + VkDevice mDevice; + VulkanResourceAllocator* mAllocator; + VkDescriptorPool mPool; + DescriptorCount const mCount; + uint16_t const mCapacity; + + // Tracks the number of allocated descriptor sets. + uint16_t mSize; + // Tracks the number of in-use descriptor sets. + uint16_t mUnusedCount; + + // This maps a layout ot a list of descriptor sets allocated for that layout. + using UnusedSetMap = std::unordered_map>, + BitmaskHashFn, BitmaskEqual>; + UnusedSetMap mUnused; + + bool mDisableRecycling; +}; + +// This is an ever-expanding pool of sets where it +// 1. Keeps a list of smaller pools of different layout-dimensions. +// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out. +class DescriptorInfinitePool { +private: + static constexpr uint16_t EXPECTED_SET_COUNT = 10; + static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5; + +public: + DescriptorInfinitePool(VkDevice device, VulkanResourceAllocator* allocator) + : mDevice(device), + mAllocator(allocator) {} + + Handle obtainSet(VulkanDescriptorSetLayout* layout) { + DescriptorPool* sameTypePool = nullptr; + for (auto& pool: mPools) { + if (!pool->canAllocate(layout)) { + continue; + } + if (auto set = pool->obtainSet(layout); set) { + return set; + } + if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) { + sameTypePool = pool.get(); + } + } + + uint16_t capacity = EXPECTED_SET_COUNT; + if (sameTypePool) { + // Exponentially increase the size of the pool to ensure we don't hit this too often. + capacity = std::ceil(sameTypePool->capacity() * SET_COUNT_GROWTH_FACTOR); + } + + // We need to increase the set of pools by one. + mPools.push_back(std::make_unique(mDevice, mAllocator, + DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity)); + auto& pool = mPools.back(); + auto ret = pool->obtainSet(layout); + assert_invariant(ret && "failed to obtain a set?"); + return ret; + } + + void disableRecycling() noexcept { + for (auto& pool: mPools) { + pool->disableRecycling(); + } + } + +private: + VkDevice mDevice; + VulkanResourceAllocator* mAllocator; + std::vector> mPools; +}; + +class LayoutCache { +private: + using Key = Bitmask; + + // Make sure the key is 8-bytes aligned. + static_assert(sizeof(Key) % 8 == 0); + + using LayoutMap = std::unordered_map, BitmaskHashFn, + BitmaskEqual>; + +public: + explicit LayoutCache(VkDevice device, VulkanResourceAllocator* allocator) + : mDevice(device), + mAllocator(allocator) {} + + ~LayoutCache() { + for (auto [key, layout]: mLayouts) { + mAllocator->destruct(layout); + } + mLayouts.clear(); + } + + void destroyLayout(Handle handle) { + for (auto [key, layout]: mLayouts) { + if (layout == handle) { + mLayouts.erase(key); + break; + } + } + mAllocator->destruct(handle); + } + + Handle getLayout(descset::DescriptorSetLayout const& layout) { + Key key = Bitmask::fromBackendLayout(layout); + if (auto iter = mLayouts.find(key); iter != mLayouts.end()) { + return iter->second; + } + + VkDescriptorSetLayoutBinding toBind[MAX_BINDINGS]; + uint32_t count = 0; + + for (auto const& binding: layout.bindings) { + VkShaderStageFlags stages = 0; + VkDescriptorType type; + + if (binding.stageFlags & descset::ShaderStageFlags2::VERTEX) { + stages |= VK_SHADER_STAGE_VERTEX_BIT; + } + if (binding.stageFlags & descset::ShaderStageFlags2::FRAGMENT) { + stages |= VK_SHADER_STAGE_FRAGMENT_BIT; + } + assert_invariant(stages != 0); + + switch (binding.type) { + case descset::DescriptorType::UNIFORM_BUFFER: { + type = binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET + ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC + : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + break; + } + case descset::DescriptorType::SAMPLER: { + type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + break; + } + case descset::DescriptorType::INPUT_ATTACHMENT: { + type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; + break; + } + } + toBind[count++] = { + .binding = binding.binding, + .descriptorType = type, + .descriptorCount = 1, + .stageFlags = stages, + }; + } + + if (count == 0) { + return {}; + } + + VkDescriptorSetLayoutCreateInfo dlinfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .bindingCount = count, + .pBindings = toBind, + }; + return (mLayouts[key] = + mAllocator->initHandle(mDevice, dlinfo, key)); + } + +private: + VkDevice mDevice; + VulkanResourceAllocator* mAllocator; + LayoutMap mLayouts; +}; + +template +struct Equal { + bool operator()(Key const& k1, Key const& k2) const { + return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(Key)); + } +}; + +// TODO: Obsolete after [GDSR]. +// No need to cache afterwards. +struct UBOKey { + uint8_t count; + uint8_t padding[5]; + uint8_t bindings[MAX_UBO_BINDING]; + // Note that the number of bytes for above is 1 + 5 + 10 = 16, which is divisible by 8. + static_assert((sizeof(count) + sizeof(padding) + sizeof(bindings)) % 8 == 0); + + VkBuffer buffers[MAX_UBO_BINDING]; + VkDeviceSize offsets[MAX_UBO_BINDING]; + VkDeviceSize sizes[MAX_UBO_BINDING]; + + static inline UBOKey key(UBOMap const& uboMap, VulkanDescriptorSetLayout* layout) { + UBOKey ret{ + .count = (uint8_t) layout->count.ubo, + }; + uint8_t count = 0; + for (uint8_t binding: layout->bindings.ubo) { + auto const& [info, obj] = uboMap[binding]; + ret.bindings[count] = binding; + if (obj) { + ret.buffers[count] = info.buffer; + ret.offsets[count] = info.offset; + ret.sizes[count] = info.range; + }// else we keep them as VK_NULL_HANDLE and 0s. + count++; + } + return ret; + } + + using HashFn = utils::hash::MurmurHashFn; + using Equal = Equal; +}; + +// TODO: Obsolete after [GDSR]. +// No need to cache afterwards. +struct SamplerKey { + uint8_t count; + uint8_t padding[7]; + uint8_t bindings[MAX_SAMPLER_BINDING]; + static_assert(sizeof(bindings) % 8 == 0); + VkSampler sampler[MAX_SAMPLER_BINDING]; + VkImageView imageView[MAX_SAMPLER_BINDING]; + VkImageLayout imageLayout[MAX_SAMPLER_BINDING]; + + static inline SamplerKey key(SamplerMap const& samplerMap, VulkanDescriptorSetLayout* layout) { + SamplerKey ret{ + .count = (uint8_t) layout->count.sampler, + }; + uint8_t count = 0; + for (uint8_t binding: layout->bindings.sampler) { + auto const& [info, obj] = samplerMap[binding]; + ret.bindings[count] = binding; + if (obj) { + ret.sampler[count] = info.sampler; + ret.imageView[count] = info.imageView; + ret.imageLayout[count] = info.imageLayout; + } // else keep them as VK_NULL_HANDLEs. + count++; + } + return ret; + } + + using HashFn = utils::hash::MurmurHashFn; + using Equal = Equal; +}; + +// TODO: Obsolete after [GDSR]. +// No need to cache afterwards. +struct InputAttachmentKey { + // This count should be fixed. + uint8_t count; + uint8_t padding[3]; + VkImageLayout imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageView view = VK_NULL_HANDLE; + + static inline InputAttachmentKey key(VkDescriptorImageInfo const& info, + VulkanDescriptorSetLayout* layout) { + return { + .count = (uint8_t) layout->count.inputAttachment, + .imageLayout = info.imageLayout, + .view = info.imageView, + }; + } + + using HashFn = utils::hash::MurmurHashFn; + using Equal = Equal; +}; + +// TODO: Obsolete after [GDSR]. +// No need to cache afterwards. +template +class LRUDescriptorSetCache { +private: + static constexpr size_t MINIMUM_DESCRIPTOR_SETS = 100; + static constexpr float LRU_REDUCTION_FACTOR = .8f; + static constexpr uint64_t N_FRAMES_AGO = 20; + +public: + using SetPtr = VulkanDescriptorSet*; + LRUDescriptorSetCache(VulkanResourceAllocator* allocator) + : mFrame(0), + mId(0), + mResources(allocator) {} + + void gc() { + mFrame++; + + // Never gc for the first N frames. + if (mFrame < N_FRAMES_AGO) { + return; + } + + uint64_t const nFramesAgo = (mFrame - N_FRAMES_AGO) << 32; + size_t const size = mCache.size(); + if (size < MINIMUM_DESCRIPTOR_SETS) { + return; + } + + auto const& popped = mLRU.pop((size_t) (size * LRU_REDUCTION_FACTOR), nFramesAgo); + for (auto p: popped) { + mCache.erase(p); + mResources.release(p); + } + } + + inline SetPtr get(Key const& key) { + if (auto itr = mCache.find(key); itr != mCache.end()) { + auto const& ret = itr->second; + mLRU.update(ret, (mFrame << 32) | mId++); + return ret; + } + return nullptr; + } + + void put(Key const& key, SetPtr set) { + mLRU.update(set, (mFrame << 32) | mId++); + mCache.put(key, set); + mResources.acquire(set); + } + + void erase(SetPtr set) { + mCache.erase(set); + mLRU.erase(set); + mResources.release(set); + } + + inline size_t size() const { + return mCache.size(); + } + +private: + struct BiMap { + using ForwardMap + = std::unordered_map; + + typename ForwardMap::const_iterator find(Key const& key) const { + return forward.find(key); + } + typename ForwardMap::const_iterator end() const { + return forward.end(); + } + + inline size_t size() const { + return forward.size(); + } + + void erase(Key const& key) { + if (auto itr = forward.find(key); itr != forward.end()) { + auto const& ptr = itr->second; + forward.erase(key); + backward.erase(ptr); + } + } + + void erase(SetPtr ptr) { + if (auto itr = backward.find(ptr); itr != backward.end()) { + auto const& key = itr->second; + forward.erase(key); + backward.erase(ptr); + } + } + + void put(Key const& key, SetPtr ptr) { + forward[key] = ptr; + backward[ptr] = key; + } + + SetPtr const& get(Key const& key) { + return forward[key]; + } + + private: + ForwardMap forward; + std::unordered_map backward; + }; + + struct PriorityQueue { + void update(SetPtr const& ptr, uint64_t priority) { + if (auto itr = backward.find(ptr); itr != backward.end()) { + auto const& priority = itr->second; + forward.erase(priority); + forward[priority] = ptr; + backward[ptr] = priority; + } else { + backward[ptr] = priority; + forward[priority] = ptr; + } + } + + void erase(SetPtr ptr) { + if (auto itr = backward.find(ptr); itr != backward.end()) { + auto const& priority = itr->second; + forward.erase(priority); + backward.erase(ptr); + } + } + + void erase(uint64_t priority) { + if (auto itr = forward.find(priority); itr != forward.end()) { + auto const& ptr = itr->second; + backward.erase(ptr); + forward.erase(itr); + } + } + + // Pop the lowest `popCount` elements that are equal or less than `priority` + utils::FixedCapacityVector pop(size_t popCount, uint64_t priority) { + utils::FixedCapacityVector evictions + = utils::FixedCapacityVector::with_capacity(popCount); + for (auto itr = forward.begin(); itr != forward.end() && popCount > 0; + itr++, popCount--) { + auto const& [ipriority, ival] = *itr; + if (ipriority > priority) { + break; + } + evictions.push_back(ival); + } + for (auto p: evictions) { + erase(p); + } + return evictions; + } + + private: + std::map forward; + std::unordered_map backward; + }; + + uint64_t mFrame; + uint64_t mId; + + BiMap mCache; + PriorityQueue mLRU; + VulkanResourceManager mResources; +}; + +// TODO: Obsolete after [GDSR]. +// No need to cache afterwards. +// The purpose of this class is to ensure that each descriptor set is only written to once, and can +// be re-bound if necessary. Therefore, we'll cache a set based on its content and return a cached +// set if we find a content match. +// It also uses a LRU heuristic for caching. The implementation of the heuristic is in the above +// class LRUDescriptorSetCache. +class DescriptorSetCache { +public: + DescriptorSetCache(VkDevice device, VulkanResourceAllocator* allocator) + : mAllocator(allocator), + mDescriptorPool(std::make_unique(device, allocator)), + mUBOCache(std::make_unique>(allocator)), + mSamplerCache(std::make_unique>(allocator)), + mInputAttachmentCache( + std::make_unique>(allocator)) {} + + template + inline std::pair get(Key const& key, + VulkanDescriptorSetLayout* layout) { + if constexpr (std::is_same_v) { + return get(key, *mUBOCache, layout); + } else if constexpr (std::is_same_v) { + return get(key, *mSamplerCache, layout); + } else if constexpr (std::is_same_v) { + return get(key, *mInputAttachmentCache, layout); + } + PANIC_POSTCONDITION("Unexpected key type"); + } + + ~DescriptorSetCache() { + // This will prevent the descriptor sets recycling when we destroy descriptor set caches. + mDescriptorPool->disableRecycling(); + + mInputAttachmentCache.reset(); + mSamplerCache.reset(); + mUBOCache.reset(); + mDescriptorPool.reset(); + } + + // gc() should be called at the end of everyframe + void gc() { + mUBOCache->gc(); + mSamplerCache->gc(); + mInputAttachmentCache->gc(); + } + +private: + template + inline std::pair get(Key const& key, + LRUDescriptorSetCache& cache, VulkanDescriptorSetLayout* layout) { + if (auto set = cache.get(key); set) { + return {set, true}; + } + auto set = mAllocator->handle_cast( + mDescriptorPool->obtainSet(layout)); + cache.put(key, set); + return {set, false}; + } + + VulkanResourceAllocator* mAllocator; + + // We need to heap-allocate so that the destruction can be strictly ordered. + std::unique_ptr mDescriptorPool; + std::unique_ptr> mUBOCache; + std::unique_ptr> mSamplerCache; + std::unique_ptr> mInputAttachmentCache; +}; + +} // anonymous namespace + +class VulkanDescriptorSetManager::Impl { +private: + using GetPipelineLayoutFunction = VulkanDescriptorSetManager::GetPipelineLayoutFunction; + using DescriptorSetVkHandles = utils::FixedCapacityVector; + + static inline DescriptorSetVkHandles initDescSetHandles() { + return DescriptorSetVkHandles::with_capacity( + VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT); + } + + struct BoundState { + BoundState() + : cmdbuf(VK_NULL_HANDLE), + pipelineLayout(VK_NULL_HANDLE), + vkSets(initDescSetHandles()) {} + + inline bool operator==(BoundState const& b) const { + if (cmdbuf != b.cmdbuf || pipelineLayout != b.pipelineLayout) { + return false; + } + for (size_t i = 0; i < vkSets.size(); ++i) { + if (vkSets[i] != b.vkSets[i]) { + return false; + } + } + return true; + } + + inline bool operator!=(BoundState const& b) const { + return !(*this == b); + } + + inline bool valid() noexcept { + return cmdbuf != VK_NULL_HANDLE; + } + + VkCommandBuffer cmdbuf; + VkPipelineLayout pipelineLayout; + DescriptorSetVkHandles vkSets; + VulkanDescriptorSetLayoutList layouts; + }; + + static constexpr uint8_t UBO_SET_ID = 0; + static constexpr uint8_t SAMPLER_SET_ID = 1; + static constexpr uint8_t INPUT_ATTACHMENT_SET_ID = 2; + +public: + Impl(VkDevice device, VulkanResourceAllocator* allocator) + : mDevice(device), + mAllocator(allocator), + mLayoutCache(device, allocator), + mDescriptorSetCache(device, allocator), + mHaveDynamicUbos(false), + mResources(allocator) {} + + VkPipelineLayout bind(VulkanCommandBuffer* commands, VulkanProgram* program, + GetPipelineLayoutFunction& getPipelineLayoutFn) { + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("bind"); + + VulkanDescriptorSetLayoutList layouts; + if (auto itr = mLayoutStash.find(program); itr != mLayoutStash.end()) { + layouts = itr->second; + } else { + auto const& layoutDescriptions = program->getLayoutDescriptionList(); + uint8_t count = 0; + for (auto const& description: layoutDescriptions) { + layouts[count++] = createLayout(description); + } + mLayoutStash[program] = layouts; + } + + VulkanDescriptorSetLayoutList outLayouts = layouts; + DescriptorSetVkHandles vkDescSets = initDescSetHandles(); + VkWriteDescriptorSet descriptorWrites[MAX_BINDINGS]; + uint32_t nwrites = 0; + + // Use placeholders when necessary + for (uint8_t i = 0; i < VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; ++i) { + if (!layouts[i]) { + if (i == INPUT_ATTACHMENT_SET_ID || + (i == SAMPLER_SET_ID && !layouts[INPUT_ATTACHMENT_SET_ID])) { + continue; + } + outLayouts[i] = getPlaceHolderLayout(i); + } else { + outLayouts[i] = layouts[i]; + auto p = mAllocator->handle_cast(layouts[i]); + if (!((i == UBO_SET_ID && p->bitmask.ubo) + || (i == SAMPLER_SET_ID && p->bitmask.sampler) + || (i == INPUT_ATTACHMENT_SET_ID && p->bitmask.inputAttachment + && mInputAttachment.first.texture))) { + outLayouts[i] = getPlaceHolderLayout(i); + } + } + } + + for (uint8_t i = 0; i < VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; ++i) { + if (!outLayouts[i]) { + continue; + } + VulkanDescriptorSetLayout* layout + = mAllocator->handle_cast(outLayouts[i]); + bool const usePlaceholder = layouts[i] != outLayouts[i]; + + auto const& [set, cached] = getSet(i, layout); + VkDescriptorSet const vkSet = set->vkSet; + commands->acquire(set); + vkDescSets.push_back(vkSet); + + // Note that we still need to bind the set, but 'cached' means that we found a set with + // the exact same content already written, and we would just bind that one instead. + // We also don't need to write to the placeholder set. + if (cached || usePlaceholder) { + continue; + } + + switch (i) { + case UBO_SET_ID: { + for (uint8_t binding: layout->bindings.ubo) { + auto const& [info, ubo] = mUboMap[binding]; + descriptorWrites[nwrites++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = ubo ? &info : &mPlaceHolderBufferInfo, + }; + if (ubo) { + set->resources.acquire(ubo); + } + } + break; + } + case SAMPLER_SET_ID: { + for (uint8_t binding: layout->bindings.sampler) { + auto const& [info, texture] = mSamplerMap[binding]; + descriptorWrites[nwrites++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = texture ? &info : &mPlaceHolderImageInfo, + }; + if (texture) { + set->resources.acquire(texture); + } + } + break; + } + case INPUT_ATTACHMENT_SET_ID: { + descriptorWrites[nwrites++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = vkSet, + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + .pImageInfo = &mInputAttachment.second, + }; + set->resources.acquire(mInputAttachment.first.texture); + break; + } + default: + PANIC_POSTCONDITION("Invalid set id=%d", i); + } + } + + if (nwrites) { + vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr); + } + + VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(outLayouts); + VkCommandBuffer const cmdbuffer = commands->buffer(); + + BoundState state{}; + state.cmdbuf = cmdbuffer; + state.pipelineLayout = pipelineLayout; + state.vkSets = vkDescSets; + state.layouts = layouts; + + if (state != mBoundState) { + vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, + vkDescSets.size(), vkDescSets.data(), 0, nullptr); + mBoundState = state; + } + + // Once bound, the resources are now ref'd in the descriptor set and the some resources can + // be released and the descriptor set is ref'd by the command buffer. + for (uint8_t i = 0; i < mSamplerMap.size(); ++i) { + auto const& [info, texture] = mSamplerMap[i]; + if (texture) { + mResources.release(texture); + } + mSamplerMap[i] = {{}, nullptr}; + } + mInputAttachment = {}; + mHaveDynamicUbos = false; + + FVK_SYSTRACE_END(); + return pipelineLayout; + } + + void dynamicBind(VulkanCommandBuffer* commands, Handle uboLayout) { + if (!mHaveDynamicUbos) { + return; + } + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("dynamic-bind"); + + assert_invariant(mBoundState.valid()); + assert_invariant(commands->buffer() == mBoundState.cmdbuf); + + auto layout = mAllocator->handle_cast( + mBoundState.layouts[UBO_SET_ID]); + + // Note that this is costly, instead just use dynamic UBOs with dynamic offsets. + auto const& [set, cached] = getSet(UBO_SET_ID, layout); + VkDescriptorSet const vkSet = set->vkSet; + + if (!cached) { + VkWriteDescriptorSet descriptorWrites[MAX_UBO_BINDING]; + uint8_t nwrites = 0; + + for (uint8_t binding: layout->bindings.ubo) { + auto const& [info, ubo] = mUboMap[binding]; + descriptorWrites[nwrites++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = ubo ? &info : &mPlaceHolderBufferInfo, + }; + if (ubo) { + set->resources.acquire(ubo); + } + } + if (nwrites > 0) { + vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr); + } + } + commands->acquire(set); + + if (mBoundState.vkSets[UBO_SET_ID] != vkSet) { + vkCmdBindDescriptorSets(mBoundState.cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, + mBoundState.pipelineLayout, 0, 1, &vkSet, 0, nullptr); + mBoundState.vkSets[UBO_SET_ID] = vkSet; + } + mHaveDynamicUbos = false; + FVK_SYSTRACE_END(); + } + + void clearProgram(VulkanProgram* program) noexcept { + mLayoutStash.erase(program); + } + + Handle createLayout( + descset::DescriptorSetLayout const& description) { + return mLayoutCache.getLayout(description); + } + + void destroyLayout(Handle layout) { + mLayoutCache.destroyLayout(layout); + } + + // Note that before [GDSR] arrives, the "update" methods stash state within this class and is + // not actually working with respect to a descriptor set. + void updateBuffer(Handle, uint8_t binding, + VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { + VkDescriptorBufferInfo const info{ + .buffer = bufferObject->buffer.getGpuBuffer(), + .offset = offset, + .range = size, + }; + mUboMap[binding] = {info, bufferObject}; + mResources.acquire(bufferObject); + + if (!mHaveDynamicUbos && mBoundState.valid()) { + mHaveDynamicUbos = true; + } + } + + void updateSampler(Handle, uint8_t binding, VulkanTexture* texture, + VkSampler sampler) noexcept { + VkDescriptorImageInfo info{ + .sampler = sampler, + }; + VkImageSubresourceRange const range = texture->getPrimaryViewRange(); + VkImageViewType const expectedType = texture->getViewType(); + if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) + && expectedType == VK_IMAGE_VIEW_TYPE_2D) { + // If the sampler is part of a mipmapped depth texture, where one of the level *can* be + // an attachment, then the sampler for this texture has the same view properties as a + // view for an attachment. Therefore, we can use getAttachmentView to get a + // corresponding VkImageView. + info.imageView = texture->getAttachmentView(range); + } else { + info.imageView = texture->getViewForType(range, expectedType); + } + info.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout()); + mSamplerMap[binding] = {info, texture}; + mResources.acquire(texture); + } + + void updateInputAttachment(Handle, VulkanAttachment attachment) noexcept { + VkDescriptorImageInfo info = { + .imageView = attachment.getImageView(VK_IMAGE_ASPECT_COLOR_BIT), + .imageLayout = ImgUtil::getVkLayout(attachment.getLayout()), + }; + mInputAttachment = {attachment, info}; + mResources.acquire(attachment.texture); + } + + void clearBuffer(uint32_t binding) { + auto const& [info, ubo] = mUboMap[binding]; + if (ubo) { + mResources.release(ubo); + } + mUboMap[binding] = {{}, nullptr}; + } + + void setPlaceHolders(VkSampler sampler, VulkanTexture* texture, + VulkanBufferObject* bufferObject) noexcept { + mPlaceHolderBufferInfo = { + .buffer = bufferObject->buffer.getGpuBuffer(), + .offset = 0, + .range = 1, + }; + mPlaceHolderImageInfo = { + .sampler = sampler, + .imageView = texture->getPrimaryImageView(), + .imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout()), + }; + } + + void clearState() noexcept { + mHaveDynamicUbos = false; + if (mInputAttachment.first.texture) { + mResources.release(mInputAttachment.first.texture); + } + mInputAttachment = {}; + mBoundState = {}; + } + + inline void gc() { + mDescriptorSetCache.gc(); + } + +private: + inline std::pair getSet(uint8_t const setIndex, + VulkanDescriptorSetLayout* layout) { + switch (setIndex) { + case UBO_SET_ID: { + auto key = UBOKey::key(mUboMap, layout); + return mDescriptorSetCache.get(key, layout); + } + case SAMPLER_SET_ID: { + auto key = SamplerKey::key(mSamplerMap, layout); + return mDescriptorSetCache.get(key, layout); + } + case INPUT_ATTACHMENT_SET_ID: { + auto key = InputAttachmentKey::key(mInputAttachment.second, layout); + return mDescriptorSetCache.get(key, layout); + } + default: + PANIC_POSTCONDITION("Invalid set-id=%d", setIndex); + } + } + + inline Handle getPlaceHolderLayout(uint8_t setID) { + if (mPlaceholderLayout[setID]) { + return mPlaceholderLayout[setID]; + } + descset::DescriptorSetLayout inputLayout { + .bindings = {{}}, + }; + switch (setID) { + case UBO_SET_ID: + inputLayout.bindings[0] = { + .type = descset::DescriptorType::UNIFORM_BUFFER, + .stageFlags = descset::ShaderStageFlags2::VERTEX, + .binding = 0, + .flags = descset::DescriptorFlags::NONE, + .count = 0, + }; + break; + case SAMPLER_SET_ID: + inputLayout.bindings[0] = { + .type = descset::DescriptorType::SAMPLER, + .stageFlags = descset::ShaderStageFlags2::FRAGMENT, + .binding = 0, + .flags = descset::DescriptorFlags::NONE, + .count = 0, + }; + break; + case INPUT_ATTACHMENT_SET_ID: + inputLayout.bindings[0] = { + .type = descset::DescriptorType::INPUT_ATTACHMENT, + .stageFlags = descset::ShaderStageFlags2::FRAGMENT, + .binding = 0, + .flags = descset::DescriptorFlags::NONE, + .count = 0, + }; + break; + default: + PANIC_POSTCONDITION("Unexpected set id=%d", setID); + } + mPlaceholderLayout[setID] = mLayoutCache.getLayout(inputLayout); + return mPlaceholderLayout[setID]; + } + + VkDevice mDevice; + VulkanResourceAllocator* mAllocator; + LayoutCache mLayoutCache; + DescriptorSetCache mDescriptorSetCache; + bool mHaveDynamicUbos; + UBOMap mUboMap; + SamplerMap mSamplerMap; + std::pair mInputAttachment; + VulkanResourceManager mResources; + VkDescriptorBufferInfo mPlaceHolderBufferInfo; + VkDescriptorImageInfo mPlaceHolderImageInfo; + std::unordered_map mLayoutStash; + BoundState mBoundState; + VulkanDescriptorSetLayoutList mPlaceholderLayout = {}; +}; + +VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device, + VulkanResourceAllocator* resourceAllocator) + : mImpl(new Impl(device, resourceAllocator)) {} + +void VulkanDescriptorSetManager::terminate() noexcept { + assert_invariant(mImpl); + delete mImpl; + mImpl = nullptr; +} + +void VulkanDescriptorSetManager::gc() { + mImpl->gc(); +} + +VkPipelineLayout VulkanDescriptorSetManager::bind(VulkanCommandBuffer* commands, + VulkanProgram* program, + VulkanDescriptorSetManager::GetPipelineLayoutFunction& getPipelineLayoutFn) { + return mImpl->bind(commands, program, getPipelineLayoutFn); +} + +void VulkanDescriptorSetManager::dynamicBind(VulkanCommandBuffer* commands, + Handle uboLayout) { + mImpl->dynamicBind(commands, uboLayout); +} + +void VulkanDescriptorSetManager::clearProgram(VulkanProgram* program) noexcept { + mImpl->clearProgram(program); +} + +Handle VulkanDescriptorSetManager::createLayout( + descset::DescriptorSetLayout const& layout) { + return mImpl->createLayout(layout); +} + +void VulkanDescriptorSetManager::destroyLayout(Handle layout) { + mImpl->destroyLayout(layout); +} + +void VulkanDescriptorSetManager::updateBuffer(Handle set, + uint8_t binding, VulkanBufferObject* bufferObject, VkDeviceSize offset, + VkDeviceSize size) noexcept { + mImpl->updateBuffer(set, binding, bufferObject, offset, size); +} + +void VulkanDescriptorSetManager::updateSampler(Handle set, + uint8_t binding, VulkanTexture* texture, VkSampler sampler) noexcept { + mImpl->updateSampler(set, binding, texture, sampler); +} + +void VulkanDescriptorSetManager::updateInputAttachment(Handle set, VulkanAttachment attachment) noexcept { + mImpl->updateInputAttachment(set, attachment); +} + +void VulkanDescriptorSetManager::clearBuffer(uint32_t bindingIndex) { + mImpl->clearBuffer(bindingIndex); +} + +void VulkanDescriptorSetManager::setPlaceHolders(VkSampler sampler, VulkanTexture* texture, + VulkanBufferObject* bufferObject) noexcept { + mImpl->setPlaceHolders(sampler, texture, bufferObject); +} + +void VulkanDescriptorSetManager::clearState() noexcept { mImpl->clearState(); } + + +}// namespace filament::backend diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h new file mode 100644 index 00000000000..2fa0b020fe1 --- /dev/null +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H +#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +namespace filament::backend { + +using namespace descset; + +// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to +// introduce descriptor set. This PR will arrive before that change is complete. As such, some of +// the methods introduced here will be obsolete, and certain logic will be generalized. + +// Abstraction over the pool and the layout cache. +class VulkanDescriptorSetManager { +public: + static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = + VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; + using GetPipelineLayoutFunction = + std::function; + + VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator); + + void terminate() noexcept; + + void gc(); + + // TODO: Obsolete after [GDSR]. + // This will write/update/bind all of the descriptor set. After [GDSR], the binding for + // descriptor sets will not depend on the layout described in the program. + VkPipelineLayout bind(VulkanCommandBuffer* commands, VulkanProgram* program, + GetPipelineLayoutFunction& getPipelineLayoutFn); + + // TODO: Obsolete after [GDSR]. + // This is to "dynamically" bind UBOs that might have offsets changed between pipeline binding + // and the draw call. We do this because UBOs for primitives that are part of the same + // renderable can be stored within one buffer. This can be a no-op if there were no range + // changes between the pipeline bind and the draw call. We will re-use applicable states + // provided within the bind() call, including the UBO descriptor set layout. TODO: make it a + // proper dynamic binding when Filament-side descriptor changes are completed. + void dynamicBind(VulkanCommandBuffer* commands, Handle uboLayout); + + // TODO: Obsolete after [GDSR]. + // Since we use program pointer as cache key, we need to clear the cache when it's freed. + void clearProgram(VulkanProgram* program) noexcept; + + Handle createLayout(descset::DescriptorSetLayout const& layout); + + void destroyLayout(Handle layout); + + void updateBuffer(Handle set, uint8_t binding, + VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept; + + void updateSampler(Handle set, uint8_t binding, + VulkanTexture* texture, VkSampler sampler) noexcept; + + void updateInputAttachment(Handle set, VulkanAttachment attachment) noexcept; + + void clearBuffer(uint32_t bindingIndex); + + void setPlaceHolders(VkSampler sampler, VulkanTexture* texture, + VulkanBufferObject* bufferObject) noexcept; + + void clearState() noexcept; + + // TODO: To be completed after [GDSR] + Handle createSet(Handle layout) { + return Handle(); + } + + // TODO: To be completed after [GDSR] + void destroySet(Handle set) {} + +private: + class Impl; + Impl* mImpl; +}; + +}// namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H diff --git a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp new file mode 100644 index 00000000000..ec96418aaf1 --- /dev/null +++ b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp @@ -0,0 +1,64 @@ +/* + * 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 "VulkanPipelineLayoutCache.h" + +#include + +namespace filament::backend { + +VkPipelineLayout VulkanPipelineLayoutCache::getLayout( + VulkanDescriptorSetLayoutList const& descriptorSetLayouts) { + PipelineLayoutKey key = {VK_NULL_HANDLE}; + uint8_t descSetLayoutCount = 0; + for (auto layoutHandle: descriptorSetLayouts) { + if (layoutHandle) { + auto layout = mAllocator->handle_cast(layoutHandle); + key[descSetLayoutCount++] = layout->vklayout; + } + } + + auto iter = mPipelineLayouts.find(key); + if (iter != mPipelineLayouts.end()) { + PipelineLayoutCacheEntry& entry = mPipelineLayouts[key]; + entry.lastUsed = mTimestamp++; + return entry.handle; + } + + VkPipelineLayoutCreateInfo info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .setLayoutCount = (uint32_t) descSetLayoutCount, + .pSetLayouts = key.data(), + .pushConstantRangeCount = 0, + }; + VkPipelineLayout layout; + vkCreatePipelineLayout(mDevice, &info, VKALLOC, &layout); + + mPipelineLayouts[key] = { + .handle = layout, + .lastUsed = mTimestamp++, + }; + return layout; +} + +void VulkanPipelineLayoutCache::terminate() noexcept { + for (auto const& [key, entry]: mPipelineLayouts) { + vkDestroyPipelineLayout(mDevice, entry.handle, VKALLOC); + } +} + +}// namespace filament::backend diff --git a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h new file mode 100644 index 00000000000..375e6124d23 --- /dev/null +++ b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H +#define TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H + +#include +#include + +#include + +#include + +namespace filament::backend { + +class VulkanPipelineLayoutCache { +public: + VulkanPipelineLayoutCache(VkDevice device, VulkanResourceAllocator* allocator) + : mDevice(device), + mAllocator(allocator), + mTimestamp(0) {} + + void terminate() noexcept; + + using PipelineLayoutKey = std::array; + + VulkanPipelineLayoutCache(VulkanPipelineLayoutCache const&) = delete; + VulkanPipelineLayoutCache& operator=(VulkanPipelineLayoutCache const&) = delete; + + VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts); + +private: + using Timestamp = uint64_t; + struct PipelineLayoutCacheEntry { + VkPipelineLayout handle; + Timestamp lastUsed; + }; + + using PipelineLayoutKeyHashFn = utils::hash::MurmurHashFn; + struct PipelineLayoutKeyEqual { + bool operator()(PipelineLayoutKey const& k1, PipelineLayoutKey const& k2) const { + return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(PipelineLayoutKey)); + } + }; + + using PipelineLayoutMap = tsl::robin_map; + + VkDevice mDevice; + VulkanResourceAllocator* mAllocator; + Timestamp mTimestamp; + PipelineLayoutMap mPipelineLayouts; +}; + +} + +#endif // TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H diff --git a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp index 3f3502ce684..ab13e8859a1 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp @@ -23,6 +23,7 @@ #include #include +#include #define SWAPCHAIN_RET_FUNC(func, handle, ...) \ if (mImpl->mSurfaceSwapChains.find(handle) != mImpl->mSurfaceSwapChains.end()) { \ @@ -158,7 +159,9 @@ ExtensionSet getInstanceExtensions() { VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, // Request these if available. +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) VK_EXT_DEBUG_UTILS_EXTENSION_NAME, +#endif VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, #if FVK_ENABLED(FVK_DEBUG_VALIDATION) @@ -181,7 +184,9 @@ ExtensionSet getInstanceExtensions() { ExtensionSet getDeviceExtensions(VkPhysicalDevice device) { std::string_view const TARGET_EXTS[] = { +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) VK_EXT_DEBUG_MARKER_EXTENSION_NAME, +#endif VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, VK_KHR_MAINTENANCE1_EXTENSION_NAME, VK_KHR_MAINTENANCE2_EXTENSION_NAME, @@ -337,36 +342,22 @@ std::tuple pruneExtensions(VkPhysicalDevice device, ExtensionSet const& instExts, ExtensionSet const& deviceExts) { ExtensionSet newInstExts = instExts; ExtensionSet newDeviceExts = deviceExts; - if (vkGetPhysicalDeviceProperties2KHR) { - VkPhysicalDeviceDriverProperties driverProperties = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES, - }; - VkPhysicalDeviceProperties2 physicalDeviceProperties2 = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, - .pNext = &driverProperties, - }; - vkGetPhysicalDeviceProperties2KHR(device, &physicalDeviceProperties2); - char* driverInfo = driverProperties.driverInfo; - - if (newInstExts.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != newInstExts.end()) { - // Workaround for Mesa drivers. See issue #6192 - if ((driverInfo && strstr(driverInfo, "Mesa"))) { - newInstExts.erase(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - } - } +#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) // debugUtils and debugMarkers extensions are used mutually exclusively. if (newInstExts.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != newInstExts.end() && newDeviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != newDeviceExts.end()) { newDeviceExts.erase(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); } +#endif +#if FVK_ENABLED(FVK_DEBUG_VALIDATION) // debugMarker must also request debugReport the instance extension. So check if that's present. if (newDeviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != newDeviceExts.end() && newInstExts.find(VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == newInstExts.end()) { newDeviceExts.erase(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); } +#endif return std::tuple(newInstExts, newDeviceExts); } @@ -671,6 +662,12 @@ Driver* VulkanPlatform::createDriver(void* sharedContext, context.mDebugMarkersSupported = deviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != deviceExts.end(); +#ifdef NDEBUG + // If we are in release build, we should not have turned on debug extensions + ASSERT_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported, + "Debug utils should not be enabled in release build."); +#endif + context.mDepthFormats = findAttachmentDepthFormats(mImpl->mPhysicalDevice); assert_invariant(context.mDepthFormats.size() > 0); diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp index af4fce092ec..82be25cd0d7 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "vulkan/VulkanConstants.h" #include "vulkan/VulkanDriverFactory.h" @@ -23,6 +24,11 @@ #include +#include + +#include +#include + #if defined(__linux__) || defined(__FreeBSD__) #define LINUX_OR_FREEBSD 1 #endif @@ -30,8 +36,6 @@ // Platform specific includes and defines #if defined(__ANDROID__) #include -#elif defined(__linux__) && defined(FILAMENT_SUPPORTS_GGP) - #include #elif defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) #include namespace { @@ -75,7 +79,7 @@ #elif defined(WIN32) // No platform specific includes #else - #error Not a supported Vulkan platform + // Not a supported Vulkan platform #endif using namespace bluevk; @@ -86,8 +90,6 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getRequiredInstanceExtensions() { VulkanPlatform::ExtensionSet ret; #if defined(__ANDROID__) ret.insert("VK_KHR_android_surface"); - #elif defined(__linux__) && defined(FILAMENT_SUPPORTS_GGP) - ret.insert(VK_GGP_STREAM_DESCRIPTOR_SURFACE_EXTENSION_NAME); #elif defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) ret.insert("VK_KHR_wayland_surface"); #elif LINUX_OR_FREEBSD && defined(FILAMENT_SUPPORTS_X11) @@ -121,20 +123,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin VkResult const result = vkCreateAndroidSurfaceKHR(instance, &createInfo, VKALLOC, (VkSurfaceKHR*) &surface); ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateAndroidSurfaceKHR error."); - #elif defined(__linux__) && defined(FILAMENT_SUPPORTS_GGP) - VkStreamDescriptorSurfaceCreateInfoGGP const surface_create_info = { - .sType = VK_STRUCTURE_TYPE_STREAM_DESCRIPTOR_SURFACE_CREATE_INFO_GGP, - .streamDescriptor = kGgpPrimaryStreamDescriptor, - }; - PFN_vkCreateStreamDescriptorSurfaceGGP fpCreateStreamDescriptorSurfaceGGP - = reinterpret_cast( - vkGetInstanceProcAddr(instance, "vkCreateStreamDescriptorSurfaceGGP")); - ASSERT_PRECONDITION(fpCreateStreamDescriptorSurfaceGGP != nullptr, - "Error getting VkInstance " - "function vkCreateStreamDescriptorSurfaceGGP"); - VkResult const result = fpCreateStreamDescriptorSurfaceGGP(instance, &surface_create_info, - nullptr, (VkSurfaceKHR*) &surface); - ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateStreamDescriptorSurfaceGGP error."); #elif defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) wl* ptrval = reinterpret_cast(nativeWindow); extent.width = ptrval->width; diff --git a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp new file mode 100644 index 00000000000..893bf3439c8 --- /dev/null +++ b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2023 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 "VulkanSpirvUtils.h" + +#include // UTILS_UNUSED_IN_RELEASE, UTILS_FALLTHROUGH +#include // assert_invariant +#include + +#include + +#include +#include +#include + +namespace filament::backend { + +namespace { + +// This function transforms an OpSpecConstant instruction into just a OpConstant instruction. +// Additionally, it will adjust the value of the constant as given by the program. +void getTransformedConstantInst(SpecConstantValue* value, uint32_t* inst) { + + // The first word is the size of the instruction and the instruction type. + // The second word is the type of the instruction. + // The third word is the "result id" (i.e. the variable to store into). + // The fourth word (only for floats and ints) is the literal representing the value. + // We only want to change the first and fourth words. + constexpr size_t const OP = 0; + constexpr size_t const VALUE = 3; + + if (!value) { + // If a specialization wasn't provided, just switch the opcode. + uint32_t const op = inst[OP] & 0x0000FFFF; + if (op == spv::Op::OpSpecConstantFalse) { + inst[OP] = (3 << 16) | spv::Op::OpConstantFalse; + } else if (op == spv::Op::OpSpecConstantTrue) { + inst[OP] = (3 << 16) | spv::Op::OpConstantTrue; + } else if (op == spv::Op::OpSpecConstant) { + inst[OP] = (4 << 16) | spv::Op::OpConstant; + } + } else if (std::holds_alternative(*value)) { + inst[OP] = (3 << 16) + | (std::get(*value) ? spv::Op::OpConstantTrue : spv::Op::OpConstantFalse); + } else if (std::holds_alternative(*value)) { + float const fval = std::get(*value); + inst[OP] = (4 << 16) | spv::Op::OpConstant; + inst[VALUE] = *reinterpret_cast(&fval); + } else { + int const ival = std::get(*value); + inst[OP] = (4 << 16) | spv::Op::OpConstant; + inst[VALUE] = *reinterpret_cast(&ival); + } +} + +} // anonymous namespace + +void workaroundSpecConstant(Program::ShaderBlob const& blob, + utils::FixedCapacityVector const& specConstants, + std::vector& output) { + using WordMap = std::unordered_map; + using SpecValueMap = std::unordered_map; + constexpr size_t HEADER_SIZE = 5; + + WordMap varToIdMap; + SpecValueMap idToValue; + + for (auto spec: specConstants) { + idToValue[spec.id] = spec.value; + } + + // The follow loop will iterate through all the instructions and look for instructions of the + // form: + // + // OpDecorate %1 SpecId 0 + // %1 = OpSpecConstantFalse %bool + // + // We want to track the mapping between the variable id %1 and the specId 0. And when seeing a + // %1 as the result, we want to change that instruction to just an OpConsant where the constant + // value is adjusted by the spec values provided in the program. + // Additionally, we will turn the SpecId decorations to no-ops. + size_t const dataSize = blob.size() / 4; + + output.resize(dataSize); + uint32_t const* data = (uint32_t*) blob.data(); + uint32_t* outputData = output.data(); + + std::memcpy(&outputData[0], &data[0], HEADER_SIZE * 4); + size_t outputCursor = HEADER_SIZE; + + for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) { + uint32_t const firstWord = data[cursor]; + uint32_t const wordCount = firstWord >> 16; + uint32_t const op = firstWord & 0x0000FFFF; + + switch(op) { + case spv::Op::OpSpecConstant: + case spv::Op::OpSpecConstantTrue: + case spv::Op::OpSpecConstantFalse: { + uint32_t const targetVar = data[cursor + 2]; + + UTILS_UNUSED_IN_RELEASE WordMap::const_iterator idItr = varToIdMap.find(targetVar); + assert_invariant(idItr != varToIdMap.end() && + "Cannot find variable previously decorated with SpecId"); + + auto valItr = idToValue.find(idItr->second); + + SpecConstantValue* val = (valItr != idToValue.end()) ? &valItr->second : nullptr; + std::memcpy(&outputData[outputCursor], &data[cursor], wordCount * 4); + getTransformedConstantInst(val, &outputData[outputCursor]); + outputCursor += wordCount; + break; + } + case spv::Op::OpDecorate: { + if (data[cursor + 2] == spv::Decoration::DecorationSpecId) { + uint32_t const targetVar = data[cursor + 1]; + uint32_t const specId = data[cursor + 3]; + varToIdMap[targetVar] = specId; + + // Note these decorations do not need to be written to the output. + break; + } + // else fallthrough and copy like all other instructions + UTILS_FALLTHROUGH; + } + default: + std::memcpy(&outputData[outputCursor], &data[cursor], wordCount * 4); + outputCursor += wordCount; + break; + } + cursor += wordCount; + } + output.resize(outputCursor); +} + +std::tuple getProgramBindings(Program::ShaderBlob const& blob) { + std::unordered_map targetToSet, targetToBinding; + + constexpr size_t HEADER_SIZE = 5; + + size_t const dataSize = blob.size() / 4; + uint32_t const* data = (uint32_t*) blob.data(); + + for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) { + uint32_t const firstWord = data[cursor]; + uint32_t const wordCount = firstWord >> 16; + uint32_t const op = firstWord & 0x0000FFFF; + + switch(op) { + case spv::Op::OpDecorate: { + if (data[cursor + 2] == spv::Decoration::DecorationDescriptorSet) { + uint32_t const targetVar = data[cursor + 1]; + uint32_t const setId = data[cursor + 3]; + targetToSet[targetVar] = setId; + } else if (data[cursor + 2] == spv::Decoration::DecorationBinding) { + uint32_t const targetVar = data[cursor + 1]; + uint32_t const binding = data[cursor + 3]; + targetToBinding[targetVar] = binding; + } + break; + } + default: + break; + } + cursor += wordCount; + } + + constexpr uint32_t UBO = 0; + constexpr uint32_t SAMPLER = 1; + constexpr uint32_t IATTACHMENT = 2; + uint32_t ubo = 0, sampler = 0, inputAttachment = 0; + + for (auto const [target, setId]: targetToSet) { + uint32_t const binding = targetToBinding[target]; + assert_invariant(binding < 32); + switch(setId) { + case UBO: + ubo |= (1 << binding); + break; + case SAMPLER: + sampler |= (1 << binding); + break; + case IATTACHMENT: + inputAttachment |= (1 << binding); + break; + default: + PANIC_POSTCONDITION("unexpected %d", (int) setId); + } + } + return {ubo, sampler, inputAttachment}; +} + +} // namespace filament::backend diff --git a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h new file mode 100644 index 00000000000..2a9f79a6118 --- /dev/null +++ b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H +#define TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H + +#include + +#include + +#include +#include + +namespace filament::backend { + +using SpecConstantValue = Program::SpecializationConstant::Type; + +// For certain drivers, using spec constant can lead to compile errors [1] or undesirable behaviors +// [2]. In those instances, we simply change the spirv and set them to constants. +// +// (Implemenation note: we cannot write to the blob because spirv-validator does not properly handle +// the Nop (no-op) instruction, and swiftshader validates the shader before compilation. So we need +// to skip those instructions instead). +// +// [1]: QC driver cannot use spec constant to size fields +// (https://github.com/google/filament/issues/6444). +// [2]: An internal driver does not DCE a block guarded by a spec-const boolean set to false +// (b/310603393). +void workaroundSpecConstant(Program::ShaderBlob const& blob, + utils::FixedCapacityVector const& specConstants, + std::vector& output); + +// bindings for UBO, samplers, input attachment +std::tuple getProgramBindings(Program::ShaderBlob const& blob); + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H diff --git a/filament/backend/test/BackendTest.cpp b/filament/backend/test/BackendTest.cpp index 70261e8bf83..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(); } @@ -86,13 +86,16 @@ void BackendTest::executeCommands() { } void BackendTest::flushAndWait() { - auto& api = getDriverApi(); - api.finish(); + getDriverApi().finish(); executeCommands(); + getDriver().purge(); } Handle BackendTest::createSwapChain() { const NativeView& view = getNativeView(); + if (!view.ptr) { + return getDriverApi().createSwapChainHeadless(view.width, view.height, 0); + } return getDriverApi().createSwapChain(view.ptr, 0); } @@ -140,7 +143,7 @@ void BackendTest::renderTriangle(Handle renderTarget, state.rasterState.depthFunc = RasterState::DepthFunc::A; state.rasterState.culling = CullingMode::NONE; - api.draw(state, triangle.getRenderPrimitive(), 1); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); } @@ -212,4 +215,3 @@ int runTests() { } } // namespace test - 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/PlatformRunner.h b/filament/backend/test/PlatformRunner.h index 7c4204b6d1d..0e0d30075ce 100644 --- a/filament/backend/test/PlatformRunner.h +++ b/filament/backend/test/PlatformRunner.h @@ -22,6 +22,9 @@ namespace test { +constexpr uint32_t const WINDOW_WIDTH = 512; +constexpr uint32_t const WINDOW_HEIGHT = 512; + // To avoid a dependency on filabridge, the Backend enum is replicated here. enum class Backend : uint8_t { OPENGL = 1, diff --git a/filament/backend/test/TrianglePrimitive.cpp b/filament/backend/test/TrianglePrimitive.cpp index a65ec208ee9..d4028057c33 100644 --- a/filament/backend/test/TrianglePrimitive.cpp +++ b/filament/backend/test/TrianglePrimitive.cpp @@ -48,7 +48,9 @@ TrianglePrimitive::TrianglePrimitive(filament::backend::DriverApi& driverApi, const size_t size = sizeof(math::float2) * 3; mBufferObject = mDriverApi.createBufferObject(size, BufferObjectBinding::VERTEX, BufferUsage::STATIC); - mVertexBuffer = mDriverApi.createVertexBuffer(1, 1, mVertexCount, attributes); + mVertexBufferInfo = mDriverApi.createVertexBufferInfo(1, 1, attributes); + mVertexBuffer = mDriverApi.createVertexBuffer(mVertexCount, mVertexBufferInfo); + mDriverApi.setVertexBufferObject(mVertexBuffer, 0, mBufferObject); BufferDescriptor vertexBufferDesc(gVertices, size); mDriverApi.updateBufferObject(mBufferObject, std::move(vertexBufferDesc), 0); @@ -60,7 +62,7 @@ TrianglePrimitive::TrianglePrimitive(filament::backend::DriverApi& driverApi, mDriverApi.updateIndexBuffer(mIndexBuffer, std::move(indexBufferDesc), 0); mRenderPrimitive = mDriverApi.createRenderPrimitive( - mVertexBuffer, mIndexBuffer, PrimitiveType::TRIANGLES, 0, 0, 2, 3); + mVertexBuffer, mIndexBuffer, PrimitiveType::TRIANGLES); } void TrianglePrimitive::updateVertices(const filament::math::float2 vertices[3]) noexcept { @@ -102,6 +104,7 @@ void TrianglePrimitive::updateIndices(const index_type* indices, int count, int TrianglePrimitive::~TrianglePrimitive() { mDriverApi.destroyBufferObject(mBufferObject); mDriverApi.destroyVertexBuffer(mVertexBuffer); + mDriverApi.destroyVertexBufferInfo(mVertexBufferInfo); mDriverApi.destroyIndexBuffer(mIndexBuffer); mDriverApi.destroyRenderPrimitive(mRenderPrimitive); } diff --git a/filament/backend/test/TrianglePrimitive.h b/filament/backend/test/TrianglePrimitive.h index aa1f6c41de0..0ea272c935d 100644 --- a/filament/backend/test/TrianglePrimitive.h +++ b/filament/backend/test/TrianglePrimitive.h @@ -40,6 +40,7 @@ class TrianglePrimitive { using PrimitiveHandle = filament::backend::Handle; using BufferObjectHandle = filament::backend::Handle; + using VertexInfoHandle = filament::backend::Handle; using VertexHandle = filament::backend::Handle; using IndexHandle = filament::backend::Handle; @@ -63,6 +64,7 @@ class TrianglePrimitive { PrimitiveHandle mRenderPrimitive; BufferObjectHandle mBufferObject; + VertexInfoHandle mVertexBufferInfo; VertexHandle mVertexBuffer; IndexHandle mIndexBuffer; diff --git a/filament/backend/test/linux_runner.cpp b/filament/backend/test/linux_runner.cpp new file mode 100644 index 00000000000..f93715ef38f --- /dev/null +++ b/filament/backend/test/linux_runner.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 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 "BackendTest.h" +#include "PlatformRunner.h" + +#include +#include + +namespace test { + +test::NativeView getNativeView() { + return { + .ptr = nullptr, + .width = WINDOW_WIDTH, + .height = WINDOW_HEIGHT, + }; +} + +}// namespace test + +namespace { + +std::array const VALID_BACKENDS{ + test::Backend::OPENGL, + test::Backend::VULKAN, +}; + +}// namespace + +int main(int argc, char* argv[]) { + auto backend = test::parseArgumentsForBackend(argc, argv); + + if (!std::any_of(VALID_BACKENDS.begin(), VALID_BACKENDS.end(), + [backend](test::Backend validBackend) { return backend == validBackend; })) { + std::cerr << "Specified an invalid backend. Only GL and Vulkan are available" << std::endl; + return 1; + } + + test::initTests(backend, false, argc, argv); + return test::runTests(); +} diff --git a/filament/backend/test/mac_runner.mm b/filament/backend/test/mac_runner.mm index 003eb853579..99205e2e7b6 100644 --- a/filament/backend/test/mac_runner.mm +++ b/filament/backend/test/mac_runner.mm @@ -58,7 +58,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { } - (NSView*)createView { - NSRect frame = NSMakeRect(0, 0, 512, 512); + NSRect frame = NSMakeRect(0, 0, test::WINDOW_WIDTH, test::WINDOW_HEIGHT); NSWindow* window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered diff --git a/filament/backend/test/test_Blit.cpp b/filament/backend/test/test_Blit.cpp index de4f5a66b34..04809884453 100644 --- a/filament/backend/test/test_Blit.cpp +++ b/filament/backend/test/test_Blit.cpp @@ -193,60 +193,6 @@ static void createFaces(DriverApi& dapi, Handle texture, int baseWidt dapi.update3DImage(texture, level, 0, 0, 0, width, height, 6, std::move(pb)); } -TEST_F(BackendTest, CubemapMinify) { - auto& api = getDriverApi(); - - Handle texture = api.createTexture( - SamplerType::SAMPLER_CUBEMAP, 4, TextureFormat::RGBA8, 1, 256, 256, 1, - TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT); - - createFaces(api, texture, 256, 256, 0, float3(0.5, 0, 0)); - - const int srcLevel = 0; - const int dstLevel = 1; - - const TextureCubemapFace srcFace = TextureCubemapFace::NEGATIVE_Y; - const TextureCubemapFace dstFace = TextureCubemapFace::NEGATIVE_Y; - - const TargetBufferInfo srcInfo = { texture, srcLevel, +srcFace }; - const TargetBufferInfo dstInfo = { texture, dstLevel, +dstFace }; - - Handle srcRenderTarget; - srcRenderTarget = api.createRenderTarget( TargetBufferFlags::COLOR, - 256 >> srcLevel, 256 >> srcLevel, 1, { srcInfo }, {}, {}); - - Handle dstRenderTarget; - dstRenderTarget = api.createRenderTarget( TargetBufferFlags::COLOR, - 256 >> dstLevel, 256 >> dstLevel, 1, { dstInfo }, {}, {}); - - api.blit(TargetBufferFlags::COLOR0, dstRenderTarget, - {0, 0, 256 >> dstLevel, 256 >> dstLevel}, srcRenderTarget, - {0, 0, 256 >> srcLevel, 256 >> srcLevel}, SamplerMagFilter::LINEAR); - - // Push through an empty frame. Note that this test does not do - // makeCurrent / commit and is therefore similar to renderStandaloneView. - api.beginFrame(0, 0); - api.endFrame(0); - - // Grab a screenshot. - ScreenshotParams params { 256 >> dstLevel, 256 >> dstLevel, "CubemapMinify.png" }; - api.beginFrame(0, 0); - dumpScreenshot(api, dstRenderTarget, ¶ms); - api.endFrame(0); - - // Wait for the ReadPixels result to come back. - api.finish(); - executeCommands(); - getDriver().purge(); - - // Cleanup. - api.destroyTexture(texture); - api.destroyRenderTarget(srcRenderTarget); - api.destroyRenderTarget(dstRenderTarget); - executeCommands(); -} - - TEST_F(BackendTest, ColorMagnify) { auto& api = getDriverApi(); @@ -263,7 +209,7 @@ TEST_F(BackendTest, ColorMagnify) { api.makeCurrent(swapChain, swapChain); // Create a source texture. - Handle srcTexture = api.createTexture( + Handle const srcTexture = api.createTexture( SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT); const bool flipY = sBackend == Backend::OPENGL; @@ -271,7 +217,7 @@ TEST_F(BackendTest, ColorMagnify) { createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY); // Create a destination texture. - Handle dstTexture = api.createTexture( + Handle const dstTexture = api.createTexture( SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1, TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT); @@ -280,14 +226,14 @@ 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. const int srcLevel = 1; - api.blit(TargetBufferFlags::COLOR0, dstRenderTargets[0], + api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTargets[0], {0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTargets[srcLevel], {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR); @@ -338,7 +284,7 @@ TEST_F(BackendTest, ColorMinify) { api.makeCurrent(swapChain, swapChain); // Create a source texture. - Handle srcTexture = api.createTexture( + Handle const srcTexture = api.createTexture( SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT); const bool flipY = sBackend == Backend::OPENGL; @@ -346,7 +292,7 @@ TEST_F(BackendTest, ColorMinify) { createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY); // Create a destination texture. - Handle dstTexture = api.createTexture( + Handle const dstTexture = api.createTexture( SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1, TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT); @@ -355,36 +301,28 @@ 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. const int srcLevel = 1; - api.blit(TargetBufferFlags::COLOR0, dstRenderTargets[0], - {0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTargets[srcLevel], - {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR); - // Push through an empty frame to allow the texture to upload and the blit to execute. - api.beginFrame(0, 0); - api.commit(swapChain); - api.endFrame(0); + api.blitDEPRECATED(TargetBufferFlags::COLOR0, + dstRenderTargets[0], {0, 0, kDstTexWidth, kDstTexHeight}, + srcRenderTargets[srcLevel], {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, + SamplerMagFilter::LINEAR); // Grab a screenshot. ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMinify.png" }; - api.beginFrame(0, 0); dumpScreenshot(api, dstRenderTargets[0], ¶ms); - api.commit(swapChain); - api.endFrame(0); // Wait for the ReadPixels result to come back. - api.finish(); - executeCommands(); - getDriver().purge(); + flushAndWait(); // Check if the image matches perfectly to our golden run. - const uint32_t expected = 0x7739bef5; + const uint32_t expected = 0xf3d9c53f; printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected); EXPECT_TRUE(params.pixelHashResult == expected); @@ -397,149 +335,6 @@ TEST_F(BackendTest, ColorMinify) { executeCommands(); } -TEST_F(BackendTest, DepthMinify) { - auto& api = getDriverApi(); - - constexpr int kSrcTexWidth = 1024; - constexpr int kSrcTexHeight = 1024; - constexpr int kDstTexWidth = 256; - constexpr int kDstTexHeight = 256; - constexpr auto kColorTexFormat = TextureFormat::RGBA8; - constexpr auto kDepthTexFormat = TextureFormat::DEPTH24; - constexpr int kNumLevels = 3; - - // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = api.createSwapChainHeadless(256, 256, 0); - api.makeCurrent(swapChain, swapChain); - - // Create a program. - ProgramHandle program; - { - ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform); - Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); - prog.uniformBlockBindings({{"params", 1}}); - program = api.createProgram(std::move(prog)); - } - - // Create a VertexBuffer, IndexBuffer, and RenderPrimitive. - TrianglePrimitive* triangle = new TrianglePrimitive(api); - - // Create textures for the first rendering pass. - Handle srcColorTexture = api.createTexture( - SamplerType::SAMPLER_2D, kNumLevels, kColorTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1, - TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT); - - Handle srcDepthTexture = api.createTexture( - SamplerType::SAMPLER_2D, 1, kDepthTexFormat, 1, kSrcTexWidth, kSrcTexHeight, - 1, TextureUsage::DEPTH_ATTACHMENT); - - // Create textures for the second rendering pass. - Handle dstColorTexture = api.createTexture( - SamplerType::SAMPLER_2D, kNumLevels, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1, - TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT); - - Handle dstDepthTexture = api.createTexture( - SamplerType::SAMPLER_2D, 1, kDepthTexFormat, 1, kDstTexWidth, kDstTexHeight, - 1, TextureUsage::DEPTH_ATTACHMENT); - - // Create render targets. - const int level = 0; - - Handle srcRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, - kSrcTexWidth >> level, kSrcTexHeight >> level, 1, - {{ srcColorTexture, level }}, - { srcDepthTexture, level }, {}); - - Handle dstRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, - kDstTexWidth >> level, kDstTexHeight >> level, 1, - {{ dstColorTexture, level }}, - { dstDepthTexture, level }, {}); - - // Prep for rendering. - RenderPassParams params = {}; - params.flags.clear = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH; - params.clearColor = float4(1, 1, 0, 1); - params.clearDepth = 1.0; - params.viewport.width = kSrcTexWidth; - params.viewport.height = kSrcTexHeight; - - PipelineState state = {}; - state.rasterState.colorWrite = true; - state.rasterState.depthWrite = true; - state.rasterState.depthFunc = RasterState::DepthFunc::L; - state.rasterState.culling = RasterState::CullingMode::NONE; - state.program = program; - auto ubuffer = api.createBufferObject(sizeof(MaterialParams), - BufferObjectBinding::UNIFORM, BufferUsage::STATIC); - api.makeCurrent(swapChain, swapChain); - api.beginFrame(0, 0); - api.bindUniformBuffer(0, ubuffer); - - // Draw red triangle into srcRenderTarget. - uploadUniforms(api, ubuffer, { - .color = float4(1, 0, 0, 1), - .scale = float4(1, 1, 0.5, 0), - }); - api.beginRenderPass(srcRenderTarget, params); - api.draw(state, triangle->getRenderPrimitive(), 1); - api.endRenderPass(); - api.endFrame(0); - - // Copy over the color buffer and the depth buffer. - api.blit(TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, dstRenderTarget, - {0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget, - {0, 0, kSrcTexWidth, kSrcTexHeight}, SamplerMagFilter::LINEAR); - - // Draw a larger blue triangle into dstRenderTarget. We carefully place it below the red - // triangle in order to exercise depth testing. - api.beginFrame(0, 0); - params.viewport.width = kDstTexWidth; - params.viewport.height = kDstTexHeight; - params.flags.clear = TargetBufferFlags::NONE; - uploadUniforms(api, ubuffer, { - .color = float4(0, 0, 1, 1), - .scale = float4(1.2, 1.2, 0.75, 0), - }); - api.beginRenderPass(dstRenderTarget, params); - api.draw(state, triangle->getRenderPrimitive(), 1); - api.endRenderPass(); - api.endFrame(0); - - // Grab a screenshot. - ScreenshotParams sparams { kDstTexWidth, kDstTexHeight, "DepthMinify.png" }; - api.beginFrame(0, 0); - dumpScreenshot(api, dstRenderTarget, &sparams); - api.commit(swapChain); - api.endFrame(0); - - // Wait for the ReadPixels result to come back. - api.finish(); - executeCommands(); - getDriver().purge(); - - // Check if the image matches perfectly to our golden run. - const uint32_t expected = 0x92b6f5c1; - printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected); - EXPECT_TRUE(sparams.pixelHashResult == expected); - - // Cleanup. - api.destroyBufferObject(ubuffer); - api.destroyProgram(program); - api.destroyTexture(srcColorTexture); - api.destroyTexture(dstColorTexture); - api.destroyTexture(srcDepthTexture); - api.destroyTexture(dstDepthTexture); - api.destroySwapChain(swapChain); - api.destroyRenderTarget(srcRenderTarget); - api.destroyRenderTarget(dstRenderTarget); - delete triangle; - executeCommands(); -} - TEST_F(BackendTest, ColorResolve) { auto& api = getDriverApi(); @@ -550,224 +345,85 @@ TEST_F(BackendTest, ColorResolve) { constexpr auto kColorTexFormat = TextureFormat::RGBA8; constexpr int kSampleCount = 4; - // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = api.createSwapChainHeadless(256, 256, 0); - api.makeCurrent(swapChain, swapChain); - // Create a program. ProgramHandle program; { ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); prog.uniformBlockBindings({{"params", 1}}); program = api.createProgram(std::move(prog)); } // Create a VertexBuffer, IndexBuffer, and RenderPrimitive. - TrianglePrimitive* triangle = new TrianglePrimitive(api); + TrianglePrimitive const triangle(api); // Create 4-sample texture. - Handle srcColorTexture = api.createTexture( + Handle const srcColorTexture = api.createTexture( SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1, TextureUsage::COLOR_ATTACHMENT); // Create 1-sample texture. - Handle dstColorTexture = api.createTexture( + Handle const dstColorTexture = api.createTexture( SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1, TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT); // Create a 4-sample render target with the 4-sample texture. - Handle srcRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount,{{ srcColorTexture }}, {}, {}); + Handle const srcRenderTarget = api.createRenderTarget( + TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount, 0, + {{ srcColorTexture }}, {}, {}); // Create a 1-sample render target with the 1-sample texture. - Handle dstRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, {{ dstColorTexture }}, {}, {}); + Handle const dstRenderTarget = api.createRenderTarget( + TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, 0, + {{ dstColorTexture }}, {}, {}); // Prep for rendering. RenderPassParams params = {}; params.flags.clear = TargetBufferFlags::COLOR; + params.flags.discardStart = TargetBufferFlags::ALL; + params.flags.discardEnd = TargetBufferFlags::NONE; params.clearColor = float4(1, 1, 0, 1); params.viewport.width = kSrcTexWidth; params.viewport.height = kSrcTexHeight; PipelineState state = {}; - state.rasterState.colorWrite = true; - state.rasterState.culling = RasterState::CullingMode::NONE; state.program = program; + state.rasterState.colorWrite = true; + state.rasterState.depthWrite = false; + state.rasterState.depthFunc = RasterState::DepthFunc::A; + state.rasterState.culling = CullingMode::NONE; + auto ubuffer = api.createBufferObject(sizeof(MaterialParams), BufferObjectBinding::UNIFORM, BufferUsage::STATIC); - api.makeCurrent(swapChain, swapChain); - api.beginFrame(0, 0); - api.bindUniformBuffer(0, ubuffer); - // Draw red triangle into srcRenderTarget. uploadUniforms(api, ubuffer, { .color = float4(1, 0, 0, 1), .scale = float4(1, 1, 0.5, 0), }); - api.beginRenderPass(srcRenderTarget, params); - api.draw(state, triangle->getRenderPrimitive(), 1); - api.endRenderPass(); - api.endFrame(0); - - // Resolve the MSAA render target into the single-sample render target. - api.blit(TargetBufferFlags::COLOR, dstRenderTarget, - {0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget, - {0, 0, kSrcTexWidth, kSrcTexHeight}, SamplerMagFilter::LINEAR); - // Grab a screenshot. - ScreenshotParams sparams { kDstTexWidth, kDstTexHeight, "ColorResolve.png" }; + // FIXME: on Metal this triangle is not drawn. Can't understand why. api.beginFrame(0, 0); - dumpScreenshot(api, dstRenderTarget, &sparams); - api.commit(swapChain); + api.beginRenderPass(srcRenderTarget, params); + api.bindUniformBuffer(0, ubuffer); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); + api.endRenderPass(); api.endFrame(0); - // Wait for the ReadPixels result to come back. - api.finish(); - executeCommands(); - getDriver().purge(); - - // Check if the image matches perfectly to our golden run. - const uint32_t expected = 0xebfac2ef; - printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected); - EXPECT_TRUE(sparams.pixelHashResult == expected); - - // Cleanup. - api.destroyBufferObject(ubuffer); - api.destroyProgram(program); - api.destroyTexture(srcColorTexture); - api.destroyTexture(dstColorTexture); - api.destroySwapChain(swapChain); - api.destroyRenderTarget(srcRenderTarget); - api.destroyRenderTarget(dstRenderTarget); - delete triangle; - executeCommands(); -} - -TEST_F(BackendTest, DepthResolve) { - auto& api = getDriverApi(); - - constexpr int kSrcTexWidth = 256; - constexpr int kSrcTexHeight = 256; - constexpr int kDstTexWidth = 256; - constexpr int kDstTexHeight = 256; - constexpr auto kColorTexFormat = TextureFormat::RGBA8; - constexpr auto kDepthTexFormat = TextureFormat::DEPTH24; - constexpr int kSampleCount = 4; - - // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = api.createSwapChainHeadless(256, 256, 0); - api.makeCurrent(swapChain, swapChain); - - // Create a program. - ProgramHandle program; - { - ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform); - Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); - prog.uniformBlockBindings({{"params", 1}}); - program = api.createProgram(std::move(prog)); - } - - // Create a VertexBuffer, IndexBuffer, and RenderPrimitive. - TrianglePrimitive* triangle = new TrianglePrimitive(api); - - // Create 4-sample textures. - Handle srcColorTexture = api.createTexture( - SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1, - TextureUsage::COLOR_ATTACHMENT); - - Handle srcDepthTexture = api.createTexture( - SamplerType::SAMPLER_2D, 1, kDepthTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1, - TextureUsage::DEPTH_ATTACHMENT); - - // Create 1-sample textures. - Handle dstColorTexture = api.createTexture( - SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1, - TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT); - - Handle dstDepthTexture = api.createTexture( - SamplerType::SAMPLER_2D, 1, kDepthTexFormat, 1, kDstTexWidth, kDstTexHeight, 1, - TextureUsage::DEPTH_ATTACHMENT); - - // Create a 4-sample render target with 4-sample textures. - Handle srcRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcTexWidth, kSrcTexHeight, - kSampleCount, {{ srcColorTexture }}, { srcDepthTexture }, {}); - - // Create a 1-sample render target with 1-sample textures. - Handle dstRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kDstTexWidth, kDstTexHeight, - 1, {{ dstColorTexture }}, { dstDepthTexture }, {}); - - // Prep for rendering. - RenderPassParams params = {}; - params.flags.clear = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH; - params.clearColor = float4(1, 1, 0, 1); - params.clearDepth = 1.0; - params.flags.discardEnd = TargetBufferFlags::NONE; - params.viewport.width = kSrcTexWidth; - params.viewport.height = kSrcTexHeight; - - PipelineState state = {}; - state.rasterState.colorWrite = true; - state.rasterState.depthWrite = true; - state.rasterState.depthFunc = RasterState::DepthFunc::L; - state.rasterState.culling = RasterState::CullingMode::NONE; - state.program = program; - auto ubuffer = api.createBufferObject(sizeof(MaterialParams), - BufferObjectBinding::UNIFORM, BufferUsage::STATIC); - api.makeCurrent(swapChain, swapChain); - api.beginFrame(0, 0); - api.bindUniformBuffer(0, ubuffer); - - // Draw red triangle into srcRenderTarget. - uploadUniforms(api, ubuffer, { - .color = float4(1, 0, 0, 1), - .scale = float4(1, 1, 0.5, 0), - }); - api.beginRenderPass(srcRenderTarget, params); - api.draw(state, triangle->getRenderPrimitive(), 1); - api.endRenderPass(); - api.endFrame(0); - - // Resolve the MSAA color and depth buffers into the single-sample render target. - api.blit(TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, dstRenderTarget, - {0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget, - {0, 0, kSrcTexWidth, kSrcTexHeight}, SamplerMagFilter::LINEAR); - - // Draw a larger blue triangle into dstRenderTarget. We carefully place it below the red - // triangle in order to exercise depth testing. - api.beginFrame(0, 0); - params.viewport.width = kDstTexWidth; - params.viewport.height = kDstTexHeight; - params.flags.clear = TargetBufferFlags::NONE; - uploadUniforms(api, ubuffer, { - .color = float4(0, 0, 1, 1), - .scale = float4(1.2, 1.2, 0.75, 0), - }); - api.beginRenderPass(dstRenderTarget, params); - api.draw(state, triangle->getRenderPrimitive(), 1); - api.endRenderPass(); + // Resolve the MSAA render target into the single-sample render target. + api.blitDEPRECATED(TargetBufferFlags::COLOR, + dstRenderTarget, {0, 0, kDstTexWidth, kDstTexHeight}, + srcRenderTarget, {0, 0, kSrcTexWidth, kSrcTexHeight}, + SamplerMagFilter::NEAREST); // Grab a screenshot. - ScreenshotParams sparams { kDstTexWidth, kDstTexHeight, "DepthResolve.png" }; - api.beginFrame(0, 0); + ScreenshotParams sparams{ kDstTexWidth, kDstTexHeight, "ColorResolve.png" }; dumpScreenshot(api, dstRenderTarget, &sparams); - api.commit(swapChain); - api.endFrame(0); // Wait for the ReadPixels result to come back. - api.finish(); - executeCommands(); - getDriver().purge(); + flushAndWait(); // Check if the image matches perfectly to our golden run. - const uint32_t expected = 0x5a38e6ba; + const uint32_t expected = 0xebfac2ef; printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected); EXPECT_TRUE(sparams.pixelHashResult == expected); @@ -776,13 +432,9 @@ TEST_F(BackendTest, DepthResolve) { api.destroyProgram(program); api.destroyTexture(srcColorTexture); api.destroyTexture(dstColorTexture); - api.destroyTexture(srcDepthTexture); - api.destroyTexture(dstDepthTexture); - api.destroySwapChain(swapChain); api.destroyRenderTarget(srcRenderTarget); api.destroyRenderTarget(dstRenderTarget); - delete triangle; - executeCommands(); + flushAndWait(); } TEST_F(BackendTest, Blit2DTextureArray) { @@ -821,13 +473,13 @@ 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; - api.blit(TargetBufferFlags::COLOR0, dstRenderTarget, + api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, {0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget, {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR); @@ -913,12 +565,12 @@ 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.blit(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget, srcRect, + api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget, srcRect, SamplerMagFilter::LINEAR); // Push through an empty frame to allow the texture to upload and the blit to execute. @@ -985,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. @@ -1005,7 +657,7 @@ TEST_F(BackendTest, BlitRegionToSwapChain) { api.beginFrame(0, 0); - api.blit(TargetBufferFlags::COLOR0, dstRenderTarget, + api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTargets[srcLevel], srcRect, SamplerMagFilter::LINEAR); diff --git a/filament/backend/test/test_BufferUpdates.cpp b/filament/backend/test/test_BufferUpdates.cpp index 27287612bfd..3207fdae9d9 100644 --- a/filament/backend/test/test_BufferUpdates.cpp +++ b/filament/backend/test/test_BufferUpdates.cpp @@ -172,7 +172,7 @@ TEST_F(BackendTest, VertexBufferUpdate) { } getDriverApi().beginRenderPass(defaultRenderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 1); + getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); getDriverApi().endRenderPass(); triangleIndex++; @@ -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 605328f100f..04f72578013 100644 --- a/filament/backend/test/test_FeedbackLoops.cpp +++ b/filament/backend/test/test_FeedbackLoops.cpp @@ -50,7 +50,7 @@ layout(location = 0) out vec4 fragColor; // Filament's Vulkan backend requires a descriptor set index of 1 for all samplers. // This parameter is ignored for other backends. -layout(location = 0, set = 1) uniform sampler2D tex; +layout(location = 0, set = 1) uniform sampler2D test_tex; uniform Params { highp float fbWidth; @@ -61,7 +61,7 @@ uniform Params { void main() { vec2 fbsize = vec2(params.fbWidth, params.fbHeight); vec2 uv = (gl_FragCoord.xy + 0.5) / fbsize; - fragColor = textureLod(tex, uv, params.sourceLevel); + fragColor = textureLod(test_tex, uv, params.sourceLevel); })"; static uint32_t sPixelHashResult = 0; @@ -112,6 +112,7 @@ static void dumpScreenshot(DriverApi& dapi, Handle rt) { std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc); ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png"); #endif + free(buffer); }; PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb); dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb)); @@ -133,24 +134,24 @@ TEST_F(BackendTest, FeedbackLoops) { // Create a program. ProgramHandle program; { - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("backend_test_sib") + SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder() + .name("Test") .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) .build(); ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform, &sib); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; + Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); prog.uniformBlockBindings({{"params", 1}}); program = api.createProgram(std::move(prog)); } - TrianglePrimitive triangle(getDriverApi()); + TrianglePrimitive const triangle(getDriverApi()); // Create a texture. auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE; - Handle texture = api.createTexture( + Handle const texture = api.createTexture( SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage); // Create a RenderTarget for each miplevel. @@ -159,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. @@ -193,7 +194,8 @@ TEST_F(BackendTest, FeedbackLoops) { sparams.filterMag = SamplerMagFilter::LINEAR; sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; samplers.setSampler(0, { texture, sparams }); - auto sgroup = api.createSamplerGroup(samplers.getSize()); + auto sgroup = + api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); auto ubuffer = api.createBufferObject(sizeof(MaterialParams), BufferObjectBinding::UNIFORM, BufferUsage::STATIC); @@ -216,7 +218,7 @@ TEST_F(BackendTest, FeedbackLoops) { .sourceLevel = float(sourceLevel), }); api.beginRenderPass(renderTargets[targetLevel], params); - api.draw(state, triangle.getRenderPrimitive(), 1); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); } @@ -235,7 +237,7 @@ TEST_F(BackendTest, FeedbackLoops) { .sourceLevel = float(sourceLevel), }); api.beginRenderPass(renderTargets[targetLevel], params); - api.draw(state, triangle.getRenderPrimitive(), 1); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); } diff --git a/filament/backend/test/test_LoadImage.cpp b/filament/backend/test/test_LoadImage.cpp index ddfab69a8a8..8e1d8f3b562 100644 --- a/filament/backend/test/test_LoadImage.cpp +++ b/filament/backend/test/test_LoadImage.cpp @@ -174,6 +174,41 @@ static const char* getSamplerTypeName(TextureFormat textureFormat) { default: return "sampler2D"; } + +} +static SamplerFormat getSamplerFormat(TextureFormat textureFormat) { + switch (textureFormat) { + case TextureFormat::R8UI: + case TextureFormat::R16UI: + case TextureFormat::RG8UI: + case TextureFormat::RGB8UI: + case TextureFormat::R32UI: + case TextureFormat::RG16UI: + case TextureFormat::RGBA8UI: + case TextureFormat::RGB16UI: + case TextureFormat::RG32UI: + case TextureFormat::RGBA16UI: + case TextureFormat::RGB32UI: + case TextureFormat::RGBA32UI: + return SamplerFormat::UINT; + + case TextureFormat::R8I: + case TextureFormat::R16I: + case TextureFormat::RG8I: + case TextureFormat::RGB8I: + case TextureFormat::R32I: + case TextureFormat::RG16I: + case TextureFormat::RGBA8I: + case TextureFormat::RGB16I: + case TextureFormat::RG32I: + case TextureFormat::RGBA16I: + case TextureFormat::RGB32I: + case TextureFormat::RGBA32I: + return SamplerFormat::INT; + + default: + return SamplerFormat::FLOAT; + } } TEST_F(BackendTest, UpdateImage2D) { @@ -231,9 +266,10 @@ TEST_F(BackendTest, UpdateImage2D) { // Test integer format uploads. // TODO: These cases fail on OpenGL and Vulkan. - testCases.emplace_back("RGB_INTEGER, UBYTE -> RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI); - testCases.emplace_back("RGB_INTEGER, USHORT -> RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI); - testCases.emplace_back("RGB_INTEGER, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I); + // TODO: These cases now also fail on Metal, but at some point previously worked. + testCases.emplace_back("RGB_INTEGER, UBYTE -> RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI); + testCases.emplace_back("RGB_INTEGER, USHORT -> RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI); + testCases.emplace_back("RGB_INTEGER, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I); // Test uploads with buffer padding. // TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)" @@ -261,49 +297,46 @@ TEST_F(BackendTest, UpdateImage2D) { auto defaultRenderTarget = api.createDefaultRenderTarget(0); // Create a program. - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() + SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder() .name("Test") .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) + .add( {{"tex", SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH }} ) .build(); - ProgramHandle program; - std::string fragment = stringReplace("{samplerType}", + + std::string const fragment = stringReplace("{samplerType}", getSamplerTypeName(t.textureFormat), fragmentTemplate); ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; + Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); - program = api.createProgram(std::move(prog)); + ProgramHandle const program = api.createProgram(std::move(prog)); // Create a Texture. - auto usage = TextureUsage::SAMPLEABLE; - Handle texture = api.createTexture(SamplerType::SAMPLER_2D, 1, + auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE; + Handle const texture = api.createTexture(SamplerType::SAMPLER_2D, 1, t.textureFormat, 1, 512, 512, 1u, usage); // Upload some pixel data. if (t.uploadSubregions) { - const auto& pf = t.pixelFormat; - const auto& pt = t.pixelType; - PixelBufferDescriptor subregion1 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding); - PixelBufferDescriptor subregion2 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding); - PixelBufferDescriptor subregion3 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding); - PixelBufferDescriptor subregion4 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding); - api.update3DImage(texture, 0, 0, 0, 0, 256, 256, 1, std::move(subregion1)); - api.update3DImage(texture, 0, 256, 0, 0, 256, 256, 1, std::move(subregion2)); - api.update3DImage(texture, 0, 0, 256, 0, 256, 256, 1, std::move(subregion3)); - api.update3DImage(texture, 0, 256, 256, 0, 256, 256, 1, std::move(subregion4)); + api.update3DImage(texture, 0, 0, 0, 0, 256, 256, 1, + checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding)); + api.update3DImage(texture, 0, 256, 0, 0, 256, 256, 1, + checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding)); + api.update3DImage(texture, 0, 0, 256, 0, 256, 256, 1, + checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding)); + api.update3DImage(texture, 0, 256, 256, 0, 256, 256, 1, + checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding)); } else { - PixelBufferDescriptor descriptor - = checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding); - api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 1, std::move(descriptor)); + api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 1, + checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding)); } SamplerGroup samplers(1); - SamplerParams sparams = {}; - sparams.filterMag = SamplerMagFilter::LINEAR; - sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; - samplers.setSampler(0, { texture, sparams }); - auto sgroup = api.createSamplerGroup(samplers.getSize()); + samplers.setSampler(0, { texture, { + .filterMag = SamplerMagFilter::NEAREST, + .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST } }); + + auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); api.bindSamplers(0, sgroup); @@ -312,7 +345,6 @@ TEST_F(BackendTest, UpdateImage2D) { readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash); - api.flush(); api.commit(swapChain); api.endFrame(0); @@ -320,25 +352,21 @@ TEST_F(BackendTest, UpdateImage2D) { api.destroySwapChain(swapChain); api.destroyRenderTarget(defaultRenderTarget); api.destroyTexture(texture); - - // This ensures all driver commands have finished before exiting the test. - api.finish(); } + api.finish(); api.stopCapture(); - executeCommands(); - - getDriver().purge(); + flushAndWait(); } TEST_F(BackendTest, UpdateImageSRGB) { auto& api = getDriverApi(); api.startCapture(); - PixelDataFormat pixelFormat = PixelDataFormat::RGB; - PixelDataType pixelType = PixelDataType::UBYTE; - TextureFormat textureFormat = TextureFormat::SRGB8_A8; + PixelDataFormat const pixelFormat = PixelDataFormat::RGBA; + PixelDataType const pixelType = PixelDataType::UBYTE; + TextureFormat const textureFormat = TextureFormat::SRGB8_A8; // Create a platform-specific SwapChain and make it current. auto swapChain = createSwapChain(); @@ -346,22 +374,22 @@ TEST_F(BackendTest, UpdateImageSRGB) { auto defaultRenderTarget = api.createDefaultRenderTarget(0); // Create a program. - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() + SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder() .name("Test") .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) .build(); - std::string fragment = stringReplace("{samplerType}", + std::string const fragment = stringReplace("{samplerType}", getSamplerTypeName(textureFormat), fragmentTemplate); ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; + Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); - ProgramHandle program = api.createProgram(std::move(prog)); + ProgramHandle const program = api.createProgram(std::move(prog)); // Create a texture. - Handle texture = api.createTexture(SamplerType::SAMPLER_2D, 1, - textureFormat, 1, 512, 512, 1, TextureUsage::SAMPLEABLE); + Handle const texture = api.createTexture(SamplerType::SAMPLER_2D, 1, + textureFormat, 1, 512, 512, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE); // Create image data. size_t components; int bpp; @@ -394,14 +422,14 @@ TEST_F(BackendTest, UpdateImageSRGB) { sparams.filterMag = SamplerMagFilter::LINEAR; sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; samplers.setSampler(0, { texture, sparams }); - auto sgroup = api.createSamplerGroup(samplers.getSize()); + auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); api.bindSamplers(0, sgroup); renderTriangle(defaultRenderTarget, swapChain, program); - static const uint32_t expectedHash = 519370995; + static const uint32_t expectedHash = 359858623; readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash); api.flush(); @@ -415,12 +443,9 @@ TEST_F(BackendTest, UpdateImageSRGB) { // This ensures all driver commands have finished before exiting the test. api.finish(); - api.stopCapture(); - executeCommands(); - - getDriver().purge(); + flushAndWait(); } TEST_F(BackendTest, UpdateImageMipLevel) { @@ -440,26 +465,26 @@ TEST_F(BackendTest, UpdateImageMipLevel) { SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() .name("Test") .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT, Precision::HIGH }} ) + .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) .build(); - std::string fragment = stringReplace("{samplerType}", + std::string const fragment = stringReplace("{samplerType}", getSamplerTypeName(textureFormat), fragmentUpdateImageMip); ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; + Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); - ProgramHandle program = api.createProgram(std::move(prog)); + ProgramHandle const program = api.createProgram(std::move(prog)); // Create a texture with 3 mip levels. // Base level: 1024 // Level 1: 512 <-- upload data and sample from this level // Level 2: 256 Handle texture = api.createTexture(SamplerType::SAMPLER_2D, 3, - textureFormat, 1, 1024, 1024, 1, TextureUsage::SAMPLEABLE); + textureFormat, 1, 1024, 1024, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE); // Create image data. PixelBufferDescriptor descriptor = checkerboardPixelBuffer(pixelFormat, pixelType, 512); - api.update3DImage(texture, 1, 0, 0, 0, 512, 512, 1, std::move(descriptor)); + api.update3DImage(texture, /* level*/ 1, 0, 0, 0, 512, 512, 1, std::move(descriptor)); api.beginFrame(0, 0); @@ -469,7 +494,7 @@ TEST_F(BackendTest, UpdateImageMipLevel) { sparams.filterMag = SamplerMagFilter::LINEAR; sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; samplers.setSampler(0, { texture, sparams }); - auto sgroup = api.createSamplerGroup(samplers.getSize()); + auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); api.bindSamplers(0, sgroup); @@ -490,12 +515,9 @@ TEST_F(BackendTest, UpdateImageMipLevel) { // This ensures all driver commands have finished before exiting the test. api.finish(); - api.stopCapture(); - executeCommands(); - - getDriver().purge(); + flushAndWait(); } TEST_F(BackendTest, UpdateImage3D) { @@ -506,7 +528,7 @@ TEST_F(BackendTest, UpdateImage3D) { PixelDataType pixelType = PixelDataType::FLOAT; TextureFormat textureFormat = TextureFormat::RGBA16F; SamplerType samplerType = SamplerType::SAMPLER_2D_ARRAY; - TextureUsage usage = TextureUsage::SAMPLEABLE; + TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE; // Create a platform-specific SwapChain and make it current. auto swapChain = createSwapChain(); @@ -517,15 +539,15 @@ TEST_F(BackendTest, UpdateImage3D) { SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() .name("Test") .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT, Precision::HIGH }} ) + .add( {{"tex", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::FLOAT, Precision::HIGH }} ) .build(); std::string fragment = stringReplace("{samplerType}", getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate); ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("tex"), 0 }; + Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); - ProgramHandle program = api.createProgram(std::move(prog)); + ProgramHandle const program = api.createProgram(std::move(prog)); // Create a texture. Handle texture = api.createTexture(samplerType, 1, @@ -556,7 +578,7 @@ TEST_F(BackendTest, UpdateImage3D) { sparams.filterMag = SamplerMagFilter::LINEAR; sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; samplers.setSampler(0, { texture, sparams}); - auto sgroup = api.createSamplerGroup(samplers.getSize()); + auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); api.bindSamplers(0, sgroup); @@ -577,12 +599,9 @@ TEST_F(BackendTest, UpdateImage3D) { // This ensures all driver commands have finished before exiting the test. api.finish(); - api.stopCapture(); - executeCommands(); - - getDriver().purge(); + flushAndWait(); } } // namespace test diff --git a/filament/backend/test/test_MRT.cpp b/filament/backend/test/test_MRT.cpp index 3e6e08f361f..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 @@ -130,7 +131,7 @@ TEST_F(BackendTest, MRT) { // Draw a triangle. getDriverApi().beginRenderPass(renderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 1); + getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); getDriverApi().endRenderPass(); getDriverApi().flush(); diff --git a/filament/backend/test/test_MipLevels.cpp b/filament/backend/test/test_MipLevels.cpp index a794003ba3f..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 = {}; @@ -168,7 +168,7 @@ TEST_F(BackendTest, SetMinMaxLevel) { ps.rasterState.colorWrite = true; ps.rasterState.depthWrite = false; api.beginRenderPass(renderTarget, params); - api.draw(ps, triangle.getRenderPrimitive(), 1); + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); } @@ -182,7 +182,6 @@ TEST_F(BackendTest, SetMinMaxLevel) { params.flags.discardEnd = TargetBufferFlags::NONE; PipelineState state; - state.scissor = params.viewport; state.program = textureProgram; state.rasterState.colorWrite = true; state.rasterState.depthWrite = false; @@ -194,7 +193,8 @@ TEST_F(BackendTest, SetMinMaxLevel) { samplerParams.filterMag = SamplerMagFilter::NEAREST; samplerParams.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST; samplers.setSampler(0, { texture, samplerParams }); - backend::Handle samplerGroup = api.createSamplerGroup(1); + backend::Handle samplerGroup = + api.createSamplerGroup(1, utils::FixedSizeString<32>("Test")); api.updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(api)); api.bindSamplers(0, samplerGroup); @@ -202,7 +202,8 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Because the min level is 1, the result color should be the white triangle drawn in the // previous pass. api.beginRenderPass(defaultRenderTarget, params); - api.draw(state, triangle.getRenderPrimitive(), 1); + api.scissor(params.viewport); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); // Adjust the base mip to 2. @@ -220,7 +221,8 @@ TEST_F(BackendTest, SetMinMaxLevel) { params.flags.clear = TargetBufferFlags::NONE; params.flags.discardStart = TargetBufferFlags::NONE; api.beginRenderPass(defaultRenderTarget, params); - api.draw(state, triangle.getRenderPrimitive(), 1); + api.scissor(params.viewport); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); api.commit(swapChain); @@ -242,4 +244,4 @@ TEST_F(BackendTest, SetMinMaxLevel) { getDriver().purge(); } -} // namespace test \ No newline at end of file +} // namespace test diff --git a/filament/backend/test/test_MissingRequiredAttributes.cpp b/filament/backend/test/test_MissingRequiredAttributes.cpp index ed1399f2a48..c56cece5993 100644 --- a/filament/backend/test/test_MissingRequiredAttributes.cpp +++ b/filament/backend/test/test_MissingRequiredAttributes.cpp @@ -104,7 +104,7 @@ TEST_F(BackendTest, MissingRequiredAttributes) { // Render a triangle. getDriverApi().beginRenderPass(defaultRenderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 1); + getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); getDriverApi().endRenderPass(); getDriverApi().flush(); diff --git a/filament/backend/test/test_ReadPixels.cpp b/filament/backend/test/test_ReadPixels.cpp index 34b2a12337e..98d8c41705d 100644 --- a/filament/backend/test/test_ReadPixels.cpp +++ b/filament/backend/test/test_ReadPixels.cpp @@ -23,6 +23,7 @@ #include #include +#include using namespace filament; using namespace filament::backend; @@ -107,7 +108,7 @@ TEST_F(ReadPixelsTest, ReadPixels) { // The size of the actual render target, taking mip level into account; size_t getRenderTargetSize () const { - return renderTargetBaseSize >> mipLevel; + return std::max(size_t(1), renderTargetBaseSize >> mipLevel); } // The rect that read pixels will read from. @@ -169,7 +170,7 @@ TEST_F(ReadPixelsTest, ReadPixels) { // The normative read pixels test case. Render a white triangle over a blue background and read // the full viewport into a pixel buffer. - TestCase t; + TestCase const t0; // Check that a subregion of the render target can be read into a pixel buffer. TestCase t2; @@ -233,7 +234,7 @@ TEST_F(ReadPixelsTest, ReadPixels) { t8.testName = "readPixels_swapchain"; t8.useDefaultRT = true; - TestCase testCases[] = { t, t2, t3, t4, t5, t6, t7, t8 }; + TestCase const testCases[] = { t0, t2, t3, t4, t5, t6, t7, t8 }; // Create programs. Handle programFloat, programUint; @@ -264,7 +265,7 @@ TEST_F(ReadPixelsTest, ReadPixels) { // Create a Texture and RenderTarget to render into. auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE; - Handle texture = getDriverApi().createTexture(SamplerType::SAMPLER_2D, + Handle const texture = getDriverApi().createTexture(SamplerType::SAMPLER_2D, t.mipLevels, t.textureFormat, 1, renderTargetBaseSize, renderTargetBaseSize, 1, usage); @@ -278,11 +279,11 @@ 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) }}, {}, {}); } - TrianglePrimitive triangle(getDriverApi()); + TrianglePrimitive const triangle(getDriverApi()); RenderPassParams params = {}; fullViewport(params); @@ -308,7 +309,7 @@ TEST_F(ReadPixelsTest, ReadPixels) { state.rasterState.depthWrite = false; state.rasterState.depthFunc = RasterState::DepthFunc::A; state.rasterState.culling = CullingMode::NONE; - getDriverApi().draw(state, triangle.getRenderPrimitive(), 1); + getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); getDriverApi().endRenderPass(); @@ -317,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(); @@ -356,7 +356,6 @@ TEST_F(ReadPixelsTest, ReadPixels) { getDriverApi().beginRenderPass(renderTarget, params); getDriverApi().endRenderPass(); - getDriverApi().flush(); getDriverApi().commit(swapChain); getDriverApi().endFrame(0); @@ -369,11 +368,7 @@ TEST_F(ReadPixelsTest, ReadPixels) { getDriverApi().destroyProgram(programUint); // This ensures all driver commands have finished before exiting the test. - getDriverApi().finish(); - - executeCommands(); - - getDriver().purge(); + flushAndWait(); } TEST_F(ReadPixelsTest, ReadPixelsPerformance) { @@ -406,6 +401,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) { renderTargetSize, // width renderTargetSize, // height 1, // samples + 0, // layerCount {{ texture }}, // color {}, // depth {}); // stencil @@ -442,7 +438,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) { // Render some content, just so we don't read back uninitialized data. getDriverApi().beginRenderPass(renderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 1); + getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); getDriverApi().endRenderPass(); PixelBufferDescriptor descriptor(buffer, renderTargetSize * renderTargetSize * 4, diff --git a/filament/backend/test/test_RenderExternalImage.cpp b/filament/backend/test/test_RenderExternalImage.cpp index 8101dd1467c..9e8d1d5fa70 100644 --- a/filament/backend/test/test_RenderExternalImage.cpp +++ b/filament/backend/test/test_RenderExternalImage.cpp @@ -45,10 +45,10 @@ std::string fragment (R"(#version 450 core layout(location = 0) out vec4 fragColor; layout(location = 0) in vec2 uv; -layout(location = 0, set = 1) uniform sampler2D tex; +layout(location = 0, set = 1) uniform sampler2D test_tex; void main() { - fragColor = texture(tex, uv); + fragColor = texture(test_tex, uv); } )"); @@ -66,7 +66,7 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) { auto swapChain = createSwapChain(); SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("backend_test_sib") + .name("Test") .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) .add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} ) .build(); @@ -74,7 +74,7 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) { // Create a program that samples a texture. Program p = shaderGen.getProgram(getDriverApi()); - Program::Sampler sampler { utils::CString("tex"), 0 }; + Program::Sampler sampler { utils::CString("test_tex"), 0 }; p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1); backend::Handle program = getDriverApi().createProgram(std::move(p)); @@ -113,13 +113,14 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) { SamplerGroup samplers(1); samplers.setSampler(0, { texture, {} }); - backend::Handle samplerGroup = getDriverApi().createSamplerGroup(1); + backend::Handle samplerGroup = + getDriverApi().createSamplerGroup(1, utils::FixedSizeString<32>("Test")); getDriverApi().updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(getDriverApi())); getDriverApi().bindSamplers(0, samplerGroup); // Render a triangle. getDriverApi().beginRenderPass(defaultRenderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 1); + getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); getDriverApi().endRenderPass(); getDriverApi().flush(); @@ -145,7 +146,7 @@ TEST_F(BackendTest, RenderExternalImage) { auto swapChain = createSwapChain(); SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("backend_test_sib") + .name("Test") .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) .add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} ) .build(); @@ -153,7 +154,7 @@ TEST_F(BackendTest, RenderExternalImage) { // Create a program that samples a texture. Program p = shaderGen.getProgram(getDriverApi()); - Program::Sampler sampler { utils::CString("tex"), 0 }; + Program::Sampler sampler { utils::CString("test_tex"), 0 }; p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1); auto program = getDriverApi().createProgram(std::move(p)); @@ -234,13 +235,14 @@ TEST_F(BackendTest, RenderExternalImage) { SamplerGroup samplers(1); samplers.setSampler(0, { texture, {} }); - backend::Handle samplerGroup = getDriverApi().createSamplerGroup(1); + backend::Handle samplerGroup = + getDriverApi().createSamplerGroup(1, utils::FixedSizeString<32>("Test")); getDriverApi().updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(getDriverApi())); getDriverApi().bindSamplers(0, samplerGroup); // Render a triangle. getDriverApi().beginRenderPass(defaultRenderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 1); + getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); getDriverApi().endRenderPass(); getDriverApi().flush(); diff --git a/filament/backend/test/test_Scissor.cpp b/filament/backend/test/test_Scissor.cpp index a7fea104bdd..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); @@ -134,13 +134,13 @@ TEST_F(BackendTest, ScissorViewportRegion) { ps.program = program; ps.rasterState.colorWrite = true; ps.rasterState.depthWrite = false; - ps.scissor = scissor; api.makeCurrent(swapChain, swapChain); api.beginFrame(0, 0); api.beginRenderPass(srcRenderTarget, params); - api.draw(ps, triangle.getRenderPrimitive(), 1); + api.scissor(scissor); + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); readPixelsAndAssertHash("scissor", kSrcTexWidth >> 1, kSrcTexHeight >> 1, fullRenderTarget, @@ -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); @@ -225,20 +225,21 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) { ps.program = program; ps.rasterState.colorWrite = true; ps.rasterState.depthWrite = false; - ps.scissor = scissor; api.makeCurrent(swapChain, swapChain); api.beginFrame(0, 0); api.beginRenderPass(renderTarget, params); - api.draw(ps, triangle.getRenderPrimitive(), 1); + api.scissor(scissor); + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); params.viewport = topLeftViewport; params.flags.clear = TargetBufferFlags::NONE; params.flags.discardStart = TargetBufferFlags::NONE; api.beginRenderPass(renderTarget, params); - api.draw(ps, triangle.getRenderPrimitive(), 1); + api.scissor(scissor); + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); readPixelsAndAssertHash( diff --git a/filament/backend/test/test_StencilBuffer.cpp b/filament/backend/test/test_StencilBuffer.cpp index 62223713100..aa02830b547 100644 --- a/filament/backend/test/test_StencilBuffer.cpp +++ b/filament/backend/test/test_StencilBuffer.cpp @@ -113,7 +113,7 @@ class BasicStencilBufferTest : public BackendTest { api.beginFrame(0, 0); api.beginRenderPass(renderTarget, params); - api.draw(ps, smallTriangle.getRenderPrimitive(), 1); + api.draw(ps, smallTriangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); // Step 2: Render a larger triangle with the stencil test enabled. @@ -126,7 +126,7 @@ class BasicStencilBufferTest : public BackendTest { ps.stencilState.front.ref = 0u; api.beginRenderPass(renderTarget, params); - api.draw(ps, triangle.getRenderPrimitive(), 1); + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); api.commit(swapChain); @@ -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); @@ -240,7 +240,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) { api.beginFrame(0, 0); api.beginRenderPass(renderTarget0, params); - api.draw(ps, smallTriangle.getRenderPrimitive(), 1); + api.draw(ps, smallTriangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); // Step 2: Render a larger triangle with the stencil test enabled. @@ -255,7 +255,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) { ps.stencilState.front.ref = 0u; api.beginRenderPass(renderTarget1, params); - api.draw(ps, triangle.getRenderPrimitive(), 1); + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); api.commit(swapChain); @@ -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/benchmark/README.md b/filament/benchmark/README.md index 7caab6eb030..01b62599558 100644 --- a/filament/benchmark/README.md +++ b/filament/benchmark/README.md @@ -6,18 +6,36 @@ ## Running the benchmark -`adb shell /data/local/tmp/benchmark_filament` +`adb shell /data/local/tmp/benchmark_filament --benchmark_counters_tabular=true` ## Benchmark results +### Macbook Pro M1 Pro +``` +-------------------------------------------------------------------------------------- +Benchmark Time CPU Iterations items_per_second +-------------------------------------------------------------------------------------- +FilamentCullingFixture/boxCulling 702 ns 702 ns 819874 729.274M/s +FilamentCullingFixture/sphereCulling 485 ns 485 ns 1430396 1054.82M/s +``` + +### Pixel 8 Pro +``` +---------------------------------------------------------------------------------------------------------------------------------- +Benchmark Time CPU Iterations BPU C CPI I items_per_second +---------------------------------------------------------------------------------------------------------------------------------- +FilamentCullingFixture/boxCulling 1212 ns 1208 ns 578797 0 6.87234 0.328354 20.9297 423.884M/s +FilamentCullingFixture/sphereCulling 748 ns 745 ns 938377 0 4.24185 0.39125 10.8418 686.839M/s +``` + ### Galaxy S20+ ``` ---------------------------------------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations BPU C CPI I items_per_second ---------------------------------------------------------------------------------------------------------------------------------- -FilamentFixture/boxCulling 1695 ns 1688 ns 414849 0 9.35888 0.422963 22.127 303.397M/s -FilamentFixture/sphereCulling 1160 ns 1147 ns 610602 0 6.35746 0.526617 12.0723 446.543M/s +FilamentCullingFixture/boxCulling 1695 ns 1688 ns 414849 0 9.35888 0.422963 22.127 303.397M/s +FilamentCullingFixture/sphereCulling 1160 ns 1147 ns 610602 0 6.35746 0.526617 12.0723 446.543M/s ``` ### Pixel 4 @@ -25,6 +43,7 @@ FilamentFixture/sphereCulling 1160 ns 1147 ns 610602 0 ---------------------------------------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations BPU C CPI I items_per_second ---------------------------------------------------------------------------------------------------------------------------------- -FilamentFixture/boxCulling 2114 ns 2106 ns 332395 0 9.93665 0.449074 22.127 243.169M/s -FilamentFixture/sphereCulling 1407 ns 1402 ns 497755 0 6.61423 0.547886 12.0723 365.3M/s +FilamentCullingFixture/boxCulling 2114 ns 2106 ns 332395 0 9.93665 0.449074 22.127 243.169M/s +FilamentCullingFixture/sphereCulling 1407 ns 1402 ns 497755 0 6.61423 0.547886 12.0723 365.3M/s ``` + diff --git a/filament/benchmark/benchmark_filament.cpp b/filament/benchmark/benchmark_filament.cpp index 3a2e6031a10..d323beed00e 100644 --- a/filament/benchmark/benchmark_filament.cpp +++ b/filament/benchmark/benchmark_filament.cpp @@ -33,7 +33,7 @@ using namespace filament::math; using namespace utils; -class FilamentFixture : public benchmark::Fixture { +class FilamentCullingFixture : public benchmark::Fixture { protected: static constexpr size_t BATCH_SIZE = 512; @@ -45,7 +45,7 @@ class FilamentFixture : public benchmark::Fixture { public: - FilamentFixture() { + FilamentCullingFixture() { std::default_random_engine gen; // NOLINT std::uniform_real_distribution rand(-100.0f, 100.0f); @@ -75,12 +75,12 @@ class FilamentFixture : public benchmark::Fixture { visibles = (Culler::result_type*)utils::aligned_alloc(batch * sizeof(*visibles), 32); } - ~FilamentFixture() override { + ~FilamentCullingFixture() override { utils::aligned_free(visibles); } }; -BENCHMARK_F(FilamentFixture, boxCulling)(benchmark::State& state) { +BENCHMARK_F(FilamentCullingFixture, boxCulling)(benchmark::State& state) { { PerformanceCounters pc(state); for (auto _ : state) { @@ -92,7 +92,7 @@ BENCHMARK_F(FilamentFixture, boxCulling)(benchmark::State& state) { } } -BENCHMARK_F(FilamentFixture, sphereCulling)(benchmark::State& state) { +BENCHMARK_F(FilamentCullingFixture, sphereCulling)(benchmark::State& state) { { PerformanceCounters pc(state); for (auto _ : state) { diff --git a/filament/include/filament/Box.h b/filament/include/filament/Box.h index 88d789859f4..da6638dabde 100644 --- a/filament/include/filament/Box.h +++ b/filament/include/filament/Box.h @@ -21,10 +21,14 @@ #include -#include - +#include #include #include +#include + +#include + +#include namespace filament { @@ -70,7 +74,7 @@ class UTILS_PUBLIC Box { * @return This bounding box */ Box& set(const math::float3& min, const math::float3& max) noexcept { - // float3 ctor needed for visual studio + // float3 ctor needed for Visual Studio center = (max + min) * math::float3(0.5f); halfExtent = (max - min) * math::float3(0.5f); return *this; @@ -130,17 +134,17 @@ class UTILS_PUBLIC Box { struct UTILS_PUBLIC Aabb { /** min coordinates */ - math::float3 min = std::numeric_limits::max(); + math::float3 min = FLT_MAX; /** max coordinates */ - math::float3 max = std::numeric_limits::lowest(); + math::float3 max = -FLT_MAX; /** * Computes the center of the box. * @return (max + min)/2 */ math::float3 center() const noexcept { - // float3 ctor needed for visual studio + // float3 ctor needed for Visual Studio return (max + min) * math::float3(0.5f); } @@ -149,7 +153,7 @@ struct UTILS_PUBLIC Aabb { * @return (max - min)/2 */ math::float3 extent() const noexcept { - // float3 ctor needed for visual studio + // float3 ctor needed for Visual Studio return (max - min) * math::float3(0.5f); } @@ -198,12 +202,14 @@ struct UTILS_PUBLIC Aabb { * @return the maximum signed distance to the box. Negative if p is in the box */ float contains(math::float3 p) const noexcept { + // we don't use std::max to avoid a dependency on + auto const maximum = [](auto a, auto b) { return a > b ? a : b; }; float d = min.x - p.x; - d = std::max(d, min.y - p.y); - d = std::max(d, min.z - p.z); - d = std::max(d, p.x - max.x); - d = std::max(d, p.y - max.y); - d = std::max(d, p.z - max.z); + d = maximum(d, min.y - p.y); + d = maximum(d, min.z - p.z); + d = maximum(d, p.x - max.x); + d = maximum(d, p.y - max.y); + d = maximum(d, p.z - max.z); return d; } diff --git a/filament/include/filament/BufferObject.h b/filament/include/filament/BufferObject.h index 1ede31b8cb0..74a4b1ff390 100644 --- a/filament/include/filament/BufferObject.h +++ b/filament/include/filament/BufferObject.h @@ -22,11 +22,13 @@ #include #include - #include #include +#include +#include + namespace filament { class FBufferObject; @@ -82,8 +84,7 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this BufferObject with. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object * * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of * memory or other resources. @@ -91,7 +92,7 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { * * @see IndexBuffer::setBuffer */ - BufferObject* build(Engine& engine); + BufferObject* UTILS_NONNULL build(Engine& engine); private: friend class FBufferObject; }; @@ -110,6 +111,10 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { * @return The maximum capacity of the BufferObject. */ size_t getByteCount() const noexcept; + +protected: + // prevent heap allocation + ~BufferObject() = default; }; } // namespace filament diff --git a/filament/include/filament/Camera.h b/filament/include/filament/Camera.h index 1c52a47cae6..74f34af0fec 100644 --- a/filament/include/filament/Camera.h +++ b/filament/include/filament/Camera.h @@ -28,6 +28,11 @@ #include #include +#include + +#include +#include + namespace utils { class Entity; } // namespace utils @@ -184,7 +189,7 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * @see Fov. */ static math::mat4 projection(Fov direction, double fovInDegrees, - double aspect, double near, double far = std::numeric_limits::infinity()); + double aspect, double near, double far = INFINITY); /** Returns the projection matrix from the focal length. * @@ -194,7 +199,7 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * @param far distance in world units from the camera to the far plane. \p far > \p near. */ static math::mat4 projection(double focalLengthInMillimeters, - double aspect, double near, double far = std::numeric_limits::infinity()); + double aspect, double near, double far = INFINITY); /** Sets the projection matrix from a frustum defined by six planes. @@ -286,19 +291,22 @@ class UTILS_PUBLIC Camera : public FilamentAPI { /** Sets a custom projection matrix for each eye. * * The projectionForCulling, near, and far parameters establish a "culling frustum" which must - * encompass anything either eye can see. + * encompass anything any eye can see. All projection matrices must be set simultaneously. The + * number of stereoscopic eyes is controlled by the stereoscopicEyeCount setting inside of + * Engine::Config. * - * @param projection an array of projection matrices, only the first - * CONFIG_STEREOSCOPIC_EYES (2) are read + * @param projection an array of projection matrices, only the first config.stereoscopicEyeCount + * are read * @param count size of the projection matrix array to set, must be - * >= CONFIG_STEREOSCOPIC_EYES (2) + * >= config.stereoscopicEyeCount * @param projectionForCulling custom projection matrix for culling, must encompass both eyes * @param near distance in world units from the camera to the culling near plane. \p near > 0. * @param far distance in world units from the camera to the culling far plane. \p far > \p * near. * @see setCustomProjection + * @see Engine::Config::stereoscopicEyeCount */ - void setCustomEyeProjection(math::mat4 const* projection, size_t count, + void setCustomEyeProjection(math::mat4 const* UTILS_NONNULL projection, size_t count, math::mat4 const& projectionForCulling, double near, double far); /** Sets an additional matrix that scales the projection matrix. @@ -357,8 +365,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * The projection matrix used for rendering always has its far plane set to infinity. This * is why it may differ from the matrix set through setProjection() or setLensProjection(). * - * @param eyeId the index of the eye to return the projection matrix for, must be < - * CONFIG_STEREOSCOPIC_EYES (2) + * @param eyeId the index of the eye to return the projection matrix for, must be + * < config.stereoscopicEyeCount * @return The projection matrix used for rendering * * @see setProjection, setLensProjection, setCustomProjection, getCullingProjectionMatrix, @@ -416,7 +424,7 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * This method is not intended to be called every frame. Instead, to update the position of the * head, use Camera::setModelMatrix. * - * @param eyeId the index of the eye to set, must be < CONFIG_STEREOSCOPIC_EYES (2) + * @param eyeId the index of the eye to set, must be < config.stereoscopicEyeCount * @param model the model matrix for an individual eye */ void setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model); @@ -565,6 +573,10 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * @return effective full field of view in degrees */ static double computeEffectiveFov(double fovInDegrees, double focusDistance) noexcept; + +protected: + // prevent heap allocation + ~Camera() = default; }; } // namespace filament diff --git a/filament/include/filament/Color.h b/filament/include/filament/Color.h index fa03b1e24c7..30b77856f25 100644 --- a/filament/include/filament/Color.h +++ b/filament/include/filament/Color.h @@ -24,6 +24,9 @@ #include #include +#include +#include + namespace filament { //! RGB color in linear space diff --git a/filament/include/filament/ColorGrading.h b/filament/include/filament/ColorGrading.h index db709600ef3..e5c8f3cae3f 100644 --- a/filament/include/filament/ColorGrading.h +++ b/filament/include/filament/ColorGrading.h @@ -26,6 +26,9 @@ #include +#include +#include + namespace filament { class Engine; @@ -200,7 +203,7 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * * @return This Builder, for chaining calls */ - Builder& toneMapper(const ToneMapper* toneMapper) noexcept; + Builder& toneMapper(ToneMapper const* UTILS_NULLABLE toneMapper) noexcept; /** * Selects the tone mapping operator to apply to the HDR color buffer as the last @@ -470,14 +473,17 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this ColorGrading with. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object. */ - ColorGrading* build(Engine& engine); + ColorGrading* UTILS_NONNULL build(Engine& engine); private: friend class FColorGrading; }; + +protected: + // prevent heap allocation + ~ColorGrading() = default; }; } // namespace filament diff --git a/filament/include/filament/ColorSpace.h b/filament/include/filament/ColorSpace.h index 502ac8db65e..d8890955053 100644 --- a/filament/include/filament/ColorSpace.h +++ b/filament/include/filament/ColorSpace.h @@ -18,7 +18,6 @@ #define TNT_FILAMENT_COLOR_SPACE_H #include -#include namespace filament::color { diff --git a/filament/include/filament/DebugRegistry.h b/filament/include/filament/DebugRegistry.h index 3fa8c5a4326..d5e1d8e983b 100644 --- a/filament/include/filament/DebugRegistry.h +++ b/filament/include/filament/DebugRegistry.h @@ -25,7 +25,7 @@ #include -#include +#include namespace filament { @@ -39,27 +39,12 @@ namespace filament { class UTILS_PUBLIC DebugRegistry : public FilamentAPI { public: - /** - * Type of a property - */ - enum Type { - BOOL, INT, FLOAT, FLOAT2, FLOAT3, FLOAT4 - }; - - /** - * Information about a property - */ - struct Property { - const char* name; //!< property name - Type type; //!< property type - }; - /** * Queries whether a property exists * @param name The name of the property to query * @return true if the property exists, false otherwise */ - bool hasProperty(const char* name) const noexcept; + bool hasProperty(const char* UTILS_NONNULL name) const noexcept; /** * Queries the address of a property's data from its name @@ -67,28 +52,30 @@ class UTILS_PUBLIC DebugRegistry : public FilamentAPI { * @return Address of the data of the \p name property * @{ */ - void* getPropertyAddress(const char* name); + void* UTILS_NULLABLE getPropertyAddress(const char* UTILS_NONNULL name); - void const* getPropertyAddress(const char* name) const noexcept; + void const* UTILS_NULLABLE getPropertyAddress(const char* UTILS_NONNULL name) const noexcept; template - inline T* getPropertyAddress(const char* name) { + inline T* UTILS_NULLABLE getPropertyAddress(const char* UTILS_NONNULL name) { return static_cast(getPropertyAddress(name)); } template - inline T const* getPropertyAddress(const char* name) const noexcept { + inline T const* UTILS_NULLABLE getPropertyAddress(const char* UTILS_NONNULL name) const noexcept { return static_cast(getPropertyAddress(name)); } template - inline bool getPropertyAddress(const char* name, T** p) { + inline bool getPropertyAddress(const char* UTILS_NONNULL name, + T* UTILS_NULLABLE* UTILS_NONNULL p) { *p = getPropertyAddress(name); return *p != nullptr; } template - inline bool getPropertyAddress(const char* name, T* const* p) const noexcept { + inline bool getPropertyAddress(const char* UTILS_NONNULL name, + T* const UTILS_NULLABLE* UTILS_NONNULL p) const noexcept { *p = getPropertyAddress(name); return *p != nullptr; } @@ -101,12 +88,12 @@ class UTILS_PUBLIC DebugRegistry : public FilamentAPI { * @return true if the operation was successful, false otherwise. * @{ */ - bool setProperty(const char* name, bool v) noexcept; - bool setProperty(const char* name, int v) noexcept; - bool setProperty(const char* name, float v) noexcept; - bool setProperty(const char* name, math::float2 v) noexcept; - bool setProperty(const char* name, math::float3 v) noexcept; - bool setProperty(const char* name, math::float4 v) noexcept; + bool setProperty(const char* UTILS_NONNULL name, bool v) noexcept; + bool setProperty(const char* UTILS_NONNULL name, int v) noexcept; + bool setProperty(const char* UTILS_NONNULL name, float v) noexcept; + bool setProperty(const char* UTILS_NONNULL name, math::float2 v) noexcept; + bool setProperty(const char* UTILS_NONNULL name, math::float3 v) noexcept; + bool setProperty(const char* UTILS_NONNULL name, math::float4 v) noexcept; /** @}*/ /** @@ -116,20 +103,20 @@ class UTILS_PUBLIC DebugRegistry : public FilamentAPI { * @return true if the call was successful and \p v was updated * @{ */ - bool getProperty(const char* name, bool* v) const noexcept; - bool getProperty(const char* name, int* v) const noexcept; - bool getProperty(const char* name, float* v) const noexcept; - bool getProperty(const char* name, math::float2* v) const noexcept; - bool getProperty(const char* name, math::float3* v) const noexcept; - bool getProperty(const char* name, math::float4* v) const noexcept; + bool getProperty(const char* UTILS_NONNULL name, bool* UTILS_NONNULL v) const noexcept; + bool getProperty(const char* UTILS_NONNULL name, int* UTILS_NONNULL v) const noexcept; + bool getProperty(const char* UTILS_NONNULL name, float* UTILS_NONNULL v) const noexcept; + bool getProperty(const char* UTILS_NONNULL name, math::float2* UTILS_NONNULL v) const noexcept; + bool getProperty(const char* UTILS_NONNULL name, math::float3* UTILS_NONNULL v) const noexcept; + bool getProperty(const char* UTILS_NONNULL name, math::float4* UTILS_NONNULL v) const noexcept; /** @}*/ struct DataSource { - void const* data; + void const* UTILS_NULLABLE data; size_t count; }; - DataSource getDataSource(const char* name) const noexcept; + DataSource getDataSource(const char* UTILS_NONNULL name) const noexcept; struct FrameHistory { using duration_ms = float; @@ -142,6 +129,10 @@ class UTILS_PUBLIC DebugRegistry : public FilamentAPI { float pid_i = 0.0f; float pid_d = 0.0f; }; + +protected: + // prevent heap allocation + ~DebugRegistry() = default; }; diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index a7b4e3c0482..b741d3abd93 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -19,9 +19,14 @@ #include +#include #include #include +#include + +#include +#include namespace utils { class Entity; @@ -172,10 +177,12 @@ class UTILS_PUBLIC Engine { using Platform = backend::Platform; using Backend = backend::Backend; using DriverConfig = backend::Platform::DriverConfig; + using FeatureLevel = backend::FeatureLevel; + using StereoscopicType = backend::StereoscopicType; /** * Config is used to define the memory footprint used by the engine, such as the - * command buffer size. Config can be used to customize engine requirements based + * command buffer size. Config can be used to customize engine requirements based * on the applications needs. * * .perRenderPassArenaSizeMB (default: 3 MiB) @@ -280,11 +287,64 @@ class UTILS_PUBLIC Engine { * the number of threads to use. */ uint32_t jobSystemThreadCount = 0; + + /* + * Number of most-recently destroyed textures to track for use-after-free. + * + * This will cause the backend to throw an exception when a texture is freed but still bound + * to a SamplerGroup and used in a draw call. 0 disables completely. + * + * Currently only respected by the Metal backend. + */ + size_t textureUseAfterFreePoolSize = 0; + + /** + * Set to `true` to forcibly disable parallel shader compilation in the backend. + * Currently only honored by the GL and Metal backends. + */ + bool disableParallelShaderCompile = false; + + /* + * The type of technique for stereoscopic rendering. + * + * This setting determines the algorithm used when stereoscopic rendering is enabled. This + * decision applies to the entire Engine for the lifetime of the Engine. E.g., multiple + * Views created from the Engine must use the same stereoscopic type. + * + * Each view can enable stereoscopic rendering via the StereoscopicOptions::enable flag. + * + * @see View::setStereoscopicOptions + */ + StereoscopicType stereoscopicType = StereoscopicType::INSTANCED; + + /* + * The number of eyes to render when stereoscopic rendering is enabled. Supported values are + * between 1 and Engine::getMaxStereoscopicEyes() (inclusive). + * + * @see View::setStereoscopicOptions + * @see Engine::getMaxStereoscopicEyes + */ + uint8_t stereoscopicEyeCount = 2; + + /* + * @deprecated This value is no longer used. + */ + uint32_t resourceAllocatorCacheSizeMB = 64; + + /* + * This value determines for how many frames are texture entries kept in the cache. + */ + uint32_t resourceAllocatorCacheMaxAge = 2; + + /* + * Disable backend handles use-after-free checks. + */ + bool disableHandleUseAfterFreeCheck = false; }; #if UTILS_HAS_THREADING - using CreateCallback = void(void* user, void* token); + using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token); #endif /** @@ -323,7 +383,7 @@ class UTILS_PUBLIC Engine { * * @return A reference to this Builder for chaining calls. */ - Builder& platform(Platform* platform) noexcept; + Builder& platform(Platform* UTILS_NULLABLE platform) noexcept; /** * @param config A pointer to optional parameters to specify memory size @@ -331,7 +391,7 @@ class UTILS_PUBLIC Engine { * * @return A reference to this Builder for chaining calls. */ - Builder& config(const Config* config) noexcept; + Builder& config(const Config* UTILS_NULLABLE config) noexcept; /** * @param sharedContext A platform-dependant context used as a shared context @@ -339,7 +399,21 @@ class UTILS_PUBLIC Engine { * * @return A reference to this Builder for chaining calls. */ - Builder& sharedContext(void* sharedContext) noexcept; + Builder& sharedContext(void* UTILS_NULLABLE sharedContext) noexcept; + + /** + * @param featureLevel The feature level at which initialize Filament. + * @return A reference to this Builder for chaining calls. + */ + Builder& featureLevel(FeatureLevel featureLevel) noexcept; + + /** + * Warning: This is an experimental API. See Engine::setPaused(bool) for caveats. + * + * @param paused Whether to start the rendering thread paused. + * @return A reference to this Builder for chaining calls. + */ + Builder& paused(bool paused) noexcept; #if UTILS_HAS_THREADING /** @@ -348,7 +422,7 @@ class UTILS_PUBLIC Engine { * @param callback Callback called once the engine is initialized and it is safe to * call Engine::getEngine(). */ - void build(utils::Invocable&& callback) const; + void build(utils::Invocable&& callback) const; #endif /** @@ -363,16 +437,17 @@ class UTILS_PUBLIC Engine { * allocate the command buffer. If exceptions are disabled, this condition if * fatal and this function will abort. */ - Engine* build() const; + Engine* UTILS_NULLABLE build() const; }; /** * Backward compatibility helper to create an Engine. * @see Builder */ - static inline Engine* create(Backend backend = Backend::DEFAULT, - Platform* platform = nullptr, void* sharedContext = nullptr, - const Config* config = nullptr) { + static inline Engine* UTILS_NULLABLE create(Backend backend = Backend::DEFAULT, + Platform* UTILS_NULLABLE platform = nullptr, + void* UTILS_NULLABLE sharedContext = nullptr, + const Config* UTILS_NULLABLE config = nullptr) { return Engine::Builder() .backend(backend) .platform(platform) @@ -387,16 +462,18 @@ class UTILS_PUBLIC Engine { * Backward compatibility helper to create an Engine asynchronously. * @see Builder */ - static inline void createAsync(CreateCallback callback, void* user, + static inline void createAsync(CreateCallback callback, + void* UTILS_NULLABLE user, Backend backend = Backend::DEFAULT, - Platform* platform = nullptr, void* sharedContext = nullptr, - const Config* config = nullptr) { + Platform* UTILS_NULLABLE platform = nullptr, + void* UTILS_NULLABLE sharedContext = nullptr, + const Config* UTILS_NULLABLE config = nullptr) { Engine::Builder() .backend(backend) .platform(platform) .sharedContext(sharedContext) .config(config) - .build([callback, user](void* token) { + .build([callback, user](void* UTILS_NONNULL token) { callback(user, token); }); } @@ -413,7 +490,7 @@ class UTILS_PUBLIC Engine { * allocate the command buffer. If exceptions are disabled, this condition if fatal and * this function will abort. */ - static Engine* getEngine(void* token); + static Engine* UTILS_NULLABLE getEngine(void* UTILS_NONNULL token); #endif @@ -443,7 +520,7 @@ class UTILS_PUBLIC Engine { * \remark * This method is thread-safe. */ - static void destroy(Engine** engine); + static void destroy(Engine* UTILS_NULLABLE* UTILS_NULLABLE engine); /** * Destroy the Engine instance and all associated resources. @@ -470,10 +547,7 @@ class UTILS_PUBLIC Engine { * \remark * This method is thread-safe. */ - static void destroy(Engine* engine); - - using FeatureLevel = backend::FeatureLevel; - + static void destroy(Engine* UTILS_NULLABLE engine); /** * Query the feature level supported by the selected backend. @@ -486,17 +560,23 @@ class UTILS_PUBLIC Engine { FeatureLevel getSupportedFeatureLevel() const noexcept; /** - * Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is - * active. The selected feature level must not be higher than the value returned by - * getActiveFeatureLevel() and it's not possible lower the active feature level. + * Activate all features of a given feature level. If an explicit feature level is not specified + * at Engine initialization time via Builder::featureLevel, the default feature level is + * FeatureLevel::FEATURE_LEVEL_0 on devices not compatible with GLES 3.0; otherwise, the default + * is FeatureLevel::FEATURE_LEVEL_1. The selected feature level must not be higher than the + * value returned by getActiveFeatureLevel() and it's not possible lower the active feature + * level. Additionally, it is not possible to modify the feature level at all if the Engine was + * initialized at FeatureLevel::FEATURE_LEVEL_0. * * @param featureLevel the feature level to activate. If featureLevel is lower than - * getActiveFeatureLevel(), the current (higher) feature level is kept. - * If featureLevel is higher than getSupportedFeatureLevel(), an exception - * is thrown, or the program is terminated if exceptions are disabled. + * getActiveFeatureLevel(), the current (higher) feature level is kept. If + * featureLevel is higher than getSupportedFeatureLevel(), or if the engine + * was initialized at feature level 0, an exception is thrown, or the + * program is terminated if exceptions are disabled. * * @return the active feature level. * + * @see Builder::featureLevel * @see getSupportedFeatureLevel * @see getActiveFeatureLevel */ @@ -527,12 +607,35 @@ class UTILS_PUBLIC Engine { size_t getMaxAutomaticInstances() const noexcept; /** - * Queries the device and platform for instanced stereo rendering support. + * Queries the device and platform for support of the given stereoscopic type. * - * @return true if stereo rendering is supported, false otherwise + * @return true if the given stereo rendering is supported, false otherwise * @see View::setStereoscopicOptions */ - bool isStereoSupported() const noexcept; + bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept; + + /** + * Retrieves the configuration settings of this Engine. + * + * This method returns the configuration object that was supplied to the Engine's + * Builder::config method during the creation of this Engine. If the Builder::config method was + * not explicitly called (or called with nullptr), this method returns the default configuration + * settings. + * + * @return a Config object with this Engine's configuration + * @see Builder::config + */ + const Config& getConfig() const noexcept; + + /** + * Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of + * eyes rendered is set at Engine creation time with the Engine::Config::stereoscopicEyeCount + * setting. + * + * @return the max number of stereoscopic eyes supported + * @see Engine::Config::stereoscopicEyeCount + */ + static size_t getMaxStereoscopicEyes() noexcept; /** * @return EntityManager used by filament @@ -592,11 +695,11 @@ class UTILS_PUBLIC Engine { * `ANativeWindow*`. * @param flags One or more configuration flags as defined in `SwapChain`. * - * @return A pointer to the newly created SwapChain or nullptr if it couldn't be created. + * @return A pointer to the newly created SwapChain. * * @see Renderer.beginFrame() */ - SwapChain* createSwapChain(void* nativeWindow, uint64_t flags = 0) noexcept; + SwapChain* UTILS_NONNULL createSwapChain(void* UTILS_NULLABLE nativeWindow, uint64_t flags = 0) noexcept; /** @@ -606,42 +709,42 @@ class UTILS_PUBLIC Engine { * @param height Height of the drawing buffer in pixels. * @param flags One or more configuration flags as defined in `SwapChain`. * - * @return A pointer to the newly created SwapChain or nullptr if it couldn't be created. + * @return A pointer to the newly created SwapChain. * * @see Renderer.beginFrame() */ - SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags = 0) noexcept; + SwapChain* UTILS_NONNULL createSwapChain(uint32_t width, uint32_t height, uint64_t flags = 0) noexcept; /** * Creates a renderer associated to this engine. * * A Renderer is intended to map to a *window* on screen. * - * @return A pointer to the newly created Renderer or nullptr if it couldn't be created. + * @return A pointer to the newly created Renderer. */ - Renderer* createRenderer() noexcept; + Renderer* UTILS_NONNULL createRenderer() noexcept; /** * Creates a View. * - * @return A pointer to the newly created View or nullptr if it couldn't be created. + * @return A pointer to the newly created View. */ - View* createView() noexcept; + View* UTILS_NONNULL createView() noexcept; /** * Creates a Scene. * - * @return A pointer to the newly created Scene or nullptr if it couldn't be created. + * @return A pointer to the newly created Scene. */ - Scene* createScene() noexcept; + Scene* UTILS_NONNULL createScene() noexcept; /** * Creates a Camera component. * * @param entity Entity to add the camera component to. - * @return A pointer to the newly created Camera or nullptr if it couldn't be created. + * @return A pointer to the newly created Camera. */ - Camera* createCamera(utils::Entity entity) noexcept; + Camera* UTILS_NONNULL createCamera(utils::Entity entity) noexcept; /** * Returns the Camera component of the given entity. @@ -651,7 +754,7 @@ class UTILS_PUBLIC Engine { * have a Camera component. The pointer is valid until destroyCameraComponent() * is called or the entity itself is destroyed. */ - Camera* getCameraComponent(utils::Entity entity) noexcept; + Camera* UTILS_NULLABLE getCameraComponent(utils::Entity entity) noexcept; /** * Destroys the Camera component associated with the given entity. @@ -663,17 +766,17 @@ class UTILS_PUBLIC Engine { /** * Creates a Fence. * - * @return A pointer to the newly created Fence or nullptr if it couldn't be created. + * @return A pointer to the newly created Fence. */ - Fence* createFence() noexcept; + Fence* UTILS_NONNULL createFence() noexcept; - bool destroy(const BufferObject* p); //!< Destroys a BufferObject object. - bool destroy(const VertexBuffer* p); //!< Destroys an VertexBuffer object. - bool destroy(const Fence* p); //!< Destroys a Fence object. - bool destroy(const IndexBuffer* p); //!< Destroys an IndexBuffer object. - bool destroy(const SkinningBuffer* p); //!< Destroys a SkinningBuffer object. - bool destroy(const MorphTargetBuffer* p); //!< Destroys a MorphTargetBuffer object. - bool destroy(const IndirectLight* p); //!< Destroys an IndirectLight object. + bool destroy(const BufferObject* UTILS_NULLABLE p); //!< Destroys a BufferObject object. + bool destroy(const VertexBuffer* UTILS_NULLABLE p); //!< Destroys an VertexBuffer object. + bool destroy(const Fence* UTILS_NULLABLE p); //!< Destroys a Fence object. + bool destroy(const IndexBuffer* UTILS_NULLABLE p); //!< Destroys an IndexBuffer object. + bool destroy(const SkinningBuffer* UTILS_NULLABLE p); //!< Destroys a SkinningBuffer object. + bool destroy(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Destroys a MorphTargetBuffer object. + bool destroy(const IndirectLight* UTILS_NULLABLE p); //!< Destroys an IndirectLight object. /** * Destroys a Material object @@ -683,38 +786,38 @@ class UTILS_PUBLIC Engine { * @exception utils::PreConditionPanic is thrown if some MaterialInstances remain. * no-op if exceptions are disabled and some MaterialInstances remain. */ - bool destroy(const Material* p); - bool destroy(const MaterialInstance* p); //!< Destroys a MaterialInstance object. - bool destroy(const Renderer* p); //!< Destroys a Renderer object. - bool destroy(const Scene* p); //!< Destroys a Scene object. - bool destroy(const Skybox* p); //!< Destroys a SkyBox object. - bool destroy(const ColorGrading* p); //!< Destroys a ColorGrading object. - bool destroy(const SwapChain* p); //!< Destroys a SwapChain object. - bool destroy(const Stream* p); //!< Destroys a Stream object. - bool destroy(const Texture* p); //!< Destroys a Texture object. - bool destroy(const RenderTarget* p); //!< Destroys a RenderTarget object. - bool destroy(const View* p); //!< Destroys a View object. - bool destroy(const InstanceBuffer* p); //!< Destroys an InstanceBuffer object. - void destroy(utils::Entity e); //!< Destroys all filament-known components from this entity - - bool isValid(const BufferObject* p); //!< Tells whether a BufferObject object is valid - bool isValid(const VertexBuffer* p); //!< Tells whether an VertexBuffer object is valid - bool isValid(const Fence* p); //!< Tells whether a Fence object is valid - bool isValid(const IndexBuffer* p); //!< Tells whether an IndexBuffer object is valid - bool isValid(const SkinningBuffer* p); //!< Tells whether a SkinningBuffer object is valid - bool isValid(const MorphTargetBuffer* p); //!< Tells whether a MorphTargetBuffer object is valid - bool isValid(const IndirectLight* p); //!< Tells whether an IndirectLight object is valid - bool isValid(const Material* p); //!< Tells whether an IndirectLight object is valid - bool isValid(const Renderer* p); //!< Tells whether a Renderer object is valid - bool isValid(const Scene* p); //!< Tells whether a Scene object is valid - bool isValid(const Skybox* p); //!< Tells whether a SkyBox object is valid - bool isValid(const ColorGrading* p); //!< Tells whether a ColorGrading object is valid - bool isValid(const SwapChain* p); //!< Tells whether a SwapChain object is valid - bool isValid(const Stream* p); //!< Tells whether a Stream object is valid - bool isValid(const Texture* p); //!< Tells whether a Texture object is valid - bool isValid(const RenderTarget* p); //!< Tells whether a RenderTarget object is valid - bool isValid(const View* p); //!< Tells whether a View object is valid - bool isValid(const InstanceBuffer* p); //!< Tells whether an InstanceBuffer object is valid + bool destroy(const Material* UTILS_NULLABLE p); + bool destroy(const MaterialInstance* UTILS_NULLABLE p); //!< Destroys a MaterialInstance object. + bool destroy(const Renderer* UTILS_NULLABLE p); //!< Destroys a Renderer object. + bool destroy(const Scene* UTILS_NULLABLE p); //!< Destroys a Scene object. + bool destroy(const Skybox* UTILS_NULLABLE p); //!< Destroys a SkyBox object. + bool destroy(const ColorGrading* UTILS_NULLABLE p); //!< Destroys a ColorGrading object. + bool destroy(const SwapChain* UTILS_NULLABLE p); //!< Destroys a SwapChain object. + bool destroy(const Stream* UTILS_NULLABLE p); //!< Destroys a Stream object. + bool destroy(const Texture* UTILS_NULLABLE p); //!< Destroys a Texture object. + bool destroy(const RenderTarget* UTILS_NULLABLE p); //!< Destroys a RenderTarget object. + bool destroy(const View* UTILS_NULLABLE p); //!< Destroys a View object. + bool destroy(const InstanceBuffer* UTILS_NULLABLE p); //!< Destroys an InstanceBuffer object. + void destroy(utils::Entity e); //!< Destroys all filament-known components from this entity + + bool isValid(const BufferObject* UTILS_NULLABLE p); //!< Tells whether a BufferObject object is valid + bool isValid(const VertexBuffer* UTILS_NULLABLE p); //!< Tells whether an VertexBuffer object is valid + bool isValid(const Fence* UTILS_NULLABLE p); //!< Tells whether a Fence object is valid + bool isValid(const IndexBuffer* UTILS_NULLABLE p); //!< Tells whether an IndexBuffer object is valid + bool isValid(const SkinningBuffer* UTILS_NULLABLE p); //!< Tells whether a SkinningBuffer object is valid + bool isValid(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Tells whether a MorphTargetBuffer object is valid + bool isValid(const IndirectLight* UTILS_NULLABLE p); //!< Tells whether an IndirectLight object is valid + bool isValid(const Material* UTILS_NULLABLE p); //!< Tells whether an IndirectLight object is valid + bool isValid(const Renderer* UTILS_NULLABLE p); //!< Tells whether a Renderer object is valid + bool isValid(const Scene* UTILS_NULLABLE p); //!< Tells whether a Scene object is valid + bool isValid(const Skybox* UTILS_NULLABLE p); //!< Tells whether a SkyBox object is valid + bool isValid(const ColorGrading* UTILS_NULLABLE p); //!< Tells whether a ColorGrading object is valid + bool isValid(const SwapChain* UTILS_NULLABLE p); //!< Tells whether a SwapChain object is valid + bool isValid(const Stream* UTILS_NULLABLE p); //!< Tells whether a Stream object is valid + bool isValid(const Texture* UTILS_NULLABLE p); //!< Tells whether a Texture object is valid + bool isValid(const RenderTarget* UTILS_NULLABLE p); //!< Tells whether a RenderTarget object is valid + bool isValid(const View* UTILS_NULLABLE p); //!< Tells whether a View object is valid + bool isValid(const InstanceBuffer* UTILS_NULLABLE p); //!< Tells whether an InstanceBuffer object is valid /** * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until @@ -737,6 +840,21 @@ class UTILS_PUBLIC Engine { */ void flush(); + /** + * Pause or resume rendering thread. + * + *

    Warning: This is an experimental API. In particular, note the following caveats. + * + *

    • + * Buffer callbacks will never be called as long as the rendering thread is paused. + * Do not rely on a buffer callback to unpause the thread. + *
    • + * While the rendering thread is paused, rendering commands will continue to be queued until the + * buffer limit is reached. When the limit is reached, the program will abort. + *
    + */ + void setPaused(bool paused); + /** * Drains the user callback message queue and immediately execute all pending callbacks. * @@ -754,7 +872,7 @@ class UTILS_PUBLIC Engine { * * @return A pointer to the default Material instance (a singleton). */ - const Material* getDefaultMaterial() const noexcept; + Material const* UTILS_NONNULL getDefaultMaterial() const noexcept; /** * Returns the resolved backend. @@ -785,7 +903,7 @@ class UTILS_PUBLIC Engine { * @return A pointer to the Platform object that was provided to Engine::create, or the * Filament-created one. */ - Platform* getPlatform() const noexcept; + Platform* UTILS_NULLABLE getPlatform() const noexcept; /** * Allocate a small amount of memory directly in the command stream. The allocated memory is @@ -798,7 +916,7 @@ class UTILS_PUBLIC Engine { * @note there is no need to destroy this buffer, it will be freed automatically when * the current command buffer is executed. */ - void* streamAlloc(size_t size, size_t alignment = alignof(double)) noexcept; + void* UTILS_NULLABLE streamAlloc(size_t size, size_t alignment = alignof(double)) noexcept; /** * Invokes one iteration of the render loop, used only on single-threaded platforms. @@ -817,14 +935,14 @@ class UTILS_PUBLIC Engine { #if defined(__EMSCRIPTEN__) /** * WebGL only: Tells the driver to reset any internal state tracking if necessary. - * - * This is only useful when integrating an external renderer into Filament on platforms + * + * This is only useful when integrating an external renderer into Filament on platforms * like WebGL, where share contexts do not exist. Filament keeps track of the GL * state it has set (like which texture is bound), and does not re-set that state if * it does not think it needs to. However, if an external renderer has set different * state in the mean time, Filament will use that new state unknowingly. - * - * If you are in this situation, call this function - ideally only once per frame, + * + * If you are in this situation, call this function - ideally only once per frame, * immediately after calling Engine::execute(). */ void resetBackendState() noexcept; diff --git a/filament/include/filament/Fence.h b/filament/include/filament/Fence.h index bcfd2871fd3..673d12cdb92 100644 --- a/filament/include/filament/Fence.h +++ b/filament/include/filament/Fence.h @@ -25,6 +25,8 @@ #include +#include + namespace filament { /** @@ -74,7 +76,11 @@ class UTILS_PUBLIC Fence : public FilamentAPI { * @return FenceStatus::CONDITION_SATISFIED on success, * FenceStatus::ERROR otherwise. */ - static FenceStatus waitAndDestroy(Fence* fence, Mode mode = Mode::FLUSH); + static FenceStatus waitAndDestroy(Fence* UTILS_NONNULL fence, Mode mode = Mode::FLUSH); + +protected: + // prevent heap allocation + ~Fence() = default; }; } // namespace filament diff --git a/filament/include/filament/FilamentAPI.h b/filament/include/filament/FilamentAPI.h index 2925aca4a5c..19d6ba246a9 100644 --- a/filament/include/filament/FilamentAPI.h +++ b/filament/include/filament/FilamentAPI.h @@ -49,8 +49,6 @@ class UTILS_PUBLIC FilamentAPI { // prevent heap allocation static void *operator new (size_t) = delete; static void *operator new[] (size_t) = delete; - static void operator delete (void*) = delete; - static void operator delete[](void*) = delete; }; template diff --git a/filament/include/filament/Frustum.h b/filament/include/filament/Frustum.h index bfd01b04362..ceec55eb823 100644 --- a/filament/include/filament/Frustum.h +++ b/filament/include/filament/Frustum.h @@ -23,9 +23,12 @@ #include #include +#include #include // Because we define NEAR and FAR in the Plane enum. +#include + namespace filament { class Box; @@ -76,14 +79,14 @@ class UTILS_PUBLIC Frustum { * @param planes six plane equations encoded as in getNormalizedPlane() in * left, right, bottom, top, far, near order */ - void getNormalizedPlanes(math::float4 planes[6]) const noexcept; + void getNormalizedPlanes(math::float4 planes[UTILS_NONNULL 6]) const noexcept; /** * Returns all six frustum planes in left, right, bottom, top, far, near order * @return six plane equations encoded as in getNormalizedPlane() in * left, right, bottom, top, far, near order */ - math::float4 const* getNormalizedPlanes() const noexcept { return mPlanes; } + math::float4 const* UTILS_NONNULL getNormalizedPlanes() const noexcept { return mPlanes; } /** * Returns whether a box intersects the frustum (i.e. is visible) diff --git a/filament/include/filament/IndexBuffer.h b/filament/include/filament/IndexBuffer.h index 09b479296d5..35b8a10ef26 100644 --- a/filament/include/filament/IndexBuffer.h +++ b/filament/include/filament/IndexBuffer.h @@ -27,6 +27,7 @@ #include +#include #include namespace filament { @@ -88,8 +89,7 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this IndexBuffer with. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object. * * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of * memory or other resources. @@ -97,7 +97,7 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { * * @see IndexBuffer::setBuffer */ - IndexBuffer* build(Engine& engine); + IndexBuffer* UTILS_NONNULL build(Engine& engine); private: friend class FIndexBuffer; }; @@ -118,6 +118,10 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { * @return The number of indices the IndexBuffer holds. */ size_t getIndexCount() const noexcept; + +protected: + // prevent heap allocation + ~IndexBuffer() = default; }; } // namespace filament diff --git a/filament/include/filament/IndirectLight.h b/filament/include/filament/IndirectLight.h index 70448523b77..c230dac8fe6 100644 --- a/filament/include/filament/IndirectLight.h +++ b/filament/include/filament/IndirectLight.h @@ -25,6 +25,8 @@ #include +#include + namespace filament { class Engine; @@ -114,7 +116,7 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * @return This Builder, for chaining calls. * */ - Builder& reflections(Texture const* cubemap) noexcept; + Builder& reflections(Texture const* UTILS_NULLABLE cubemap) noexcept; /** * Sets the irradiance as Spherical Harmonics. @@ -160,7 +162,7 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * Because the coefficients are pre-scaled, `sh[0]` is the environment's * average irradiance. */ - Builder& irradiance(uint8_t bands, math::float3 const* sh) noexcept; + Builder& irradiance(uint8_t bands, math::float3 const* UTILS_NONNULL sh) noexcept; /** * Sets the irradiance from the radiance expressed as Spherical Harmonics. @@ -192,7 +194,7 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * * @return This Builder, for chaining calls. */ - Builder& radiance(uint8_t bands, math::float3 const* sh) noexcept; + Builder& radiance(uint8_t bands, math::float3 const* UTILS_NONNULL sh) noexcept; /** * Sets the irradiance as a cubemap. @@ -211,7 +213,7 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * * @see irradiance(uint8_t bands, math::float3 const* sh) */ - Builder& irradiance(Texture const* cubemap) noexcept; + Builder& irradiance(Texture const* UTILS_NULLABLE cubemap) noexcept; /** * (optional) Environment intensity. @@ -247,7 +249,7 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * memory or other resources. * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. */ - IndirectLight* build(Engine& engine); + IndirectLight* UTILS_NONNULL build(Engine& engine); private: friend class FIndirectLight; @@ -284,12 +286,12 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { /** * Returns the associated reflection map, or null if it does not exist. */ - Texture const* getReflectionsTexture() const noexcept; + Texture const* UTILS_NULLABLE getReflectionsTexture() const noexcept; /** * Returns the associated irradiance map, or null if it does not exist. */ - Texture const* getIrradianceTexture() const noexcept; + Texture const* UTILS_NULLABLE getIrradianceTexture() const noexcept; /** * Helper to estimate the direction of the dominant light in the environment represented by @@ -312,7 +314,7 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * @see LightManager::Builder::direction() * @see getColorEstimate() */ - static math::float3 getDirectionEstimate(const math::float3 sh[9]) noexcept; + static math::float3 getDirectionEstimate(const math::float3 sh[UTILS_NONNULL 9]) noexcept; /** * Helper to estimate the color and relative intensity of the environment represented by @@ -332,7 +334,8 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * @see LightManager::Builder::intensity() * @see getDirectionEstimate, getIntensity, setIntensity */ - static math::float4 getColorEstimate(const math::float3 sh[9], math::float3 direction) noexcept; + static math::float4 getColorEstimate(const math::float3 sh[UTILS_NONNULL 9], + math::float3 direction) noexcept; /** @deprecated use static versions instead */ @@ -342,6 +345,10 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { /** @deprecated use static versions instead */ UTILS_DEPRECATED math::float4 getColorEstimate(math::float3 direction) const noexcept; + +protected: + // prevent heap allocation + ~IndirectLight() = default; }; } // namespace filament diff --git a/filament/include/filament/InstanceBuffer.h b/filament/include/filament/InstanceBuffer.h index d1ad29a9088..2135152d883 100644 --- a/filament/include/filament/InstanceBuffer.h +++ b/filament/include/filament/InstanceBuffer.h @@ -18,11 +18,14 @@ #define TNT_FILAMENT_INSTANCEBUFFER_H #include - #include +#include + #include +#include + namespace filament { /** @@ -45,7 +48,7 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { * >= 1 and <= \c Engine::getMaxAutomaticInstances() * @see Engine::getMaxAutomaticInstances */ - Builder(size_t instanceCount) noexcept; + explicit Builder(size_t instanceCount) noexcept; Builder(Builder const& rhs) noexcept; Builder(Builder&& rhs) noexcept; @@ -65,12 +68,12 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { * @param localTransforms an array of math::mat4f with length instanceCount, must remain * valid until after build() is called */ - Builder& localTransforms(math::mat4f const* localTransforms) noexcept; + Builder& localTransforms(math::mat4f const* UTILS_NULLABLE localTransforms) noexcept; /** * Creates the InstanceBuffer object and returns a pointer to it. */ - InstanceBuffer* build(Engine& engine); + InstanceBuffer* UTILS_NONNULL build(Engine& engine); private: friend class FInstanceBuffer; @@ -90,7 +93,12 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { * @param count the number of local transforms * @param offset index of the first instance to set local transforms */ - void setLocalTransforms(math::mat4f const* localTransforms, size_t count, size_t offset = 0); + void setLocalTransforms(math::mat4f const* UTILS_NONNULL localTransforms, + size_t count, size_t offset = 0); + +protected: + // prevent heap allocation + ~InstanceBuffer() = default; }; } // namespace filament diff --git a/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index b7cb62e16a1..22d663f2ea4 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -27,6 +27,9 @@ #include #include +#include +#include + namespace utils { class Entity; } // namespace utils @@ -143,20 +146,13 @@ class UTILS_PUBLIC LightManager : public FilamentAPI { using Instance = utils::EntityInstance; /** - * Returns the number of component in the LightManager, not that component are not + * Returns the number of component in the LightManager, note that component are not * guaranteed to be active. Use the EntityManager::isAlive() before use if needed. * * @return number of component in the LightManager */ size_t getComponentCount() const noexcept; - /** - * Returns the list of Entity for all components. Use getComponentCount() to know the size - * of the list. - * @return a pointer to Entity - */ - utils::Entity const* getEntities() const noexcept; - /** * Returns whether a particular Entity is associated with a component of this LightManager * @param e An Entity. @@ -164,6 +160,24 @@ class UTILS_PUBLIC LightManager : public FilamentAPI { */ bool hasComponent(utils::Entity e) const noexcept; + /** + * @return true if the this manager has no components + */ + bool empty() const noexcept; + + /** + * Retrieve the `Entity` of the component from its `Instance`. + * @param i Instance of the component obtained from getInstance() + * @return + */ + utils::Entity getEntity(Instance i) const noexcept; + + /** + * Retrieve the Entities of all the components of this manager. + * @return A list, in no particular order, of all the entities managed by this manager. + */ + utils::Entity const* UTILS_NONNULL getEntities() const noexcept; + /** * Gets an Instance representing the Light component associated with the given Entity. * @param e An Entity. @@ -380,7 +394,7 @@ class UTILS_PUBLIC LightManager : public FilamentAPI { * positions into * @param cascades the number of shadow cascades, at most 4 */ - static void computeUniformSplits(float* splitPositions, uint8_t cascades); + static void computeUniformSplits(float* UTILS_NONNULL splitPositions, uint8_t cascades); /** * Utility method to compute ShadowOptions::cascadeSplitPositions according to a logarithmic @@ -392,7 +406,7 @@ class UTILS_PUBLIC LightManager : public FilamentAPI { * @param near the camera near plane * @param far the camera far plane */ - static void computeLogSplits(float* splitPositions, uint8_t cascades, + static void computeLogSplits(float* UTILS_NONNULL splitPositions, uint8_t cascades, float near, float far); /** @@ -412,7 +426,7 @@ class UTILS_PUBLIC LightManager : public FilamentAPI { * @param lambda a float in the range [0, 1] that interpolates between log and * uniform split schemes */ - static void computePracticalSplits(float* splitPositions, uint8_t cascades, + static void computePracticalSplits(float* UTILS_NONNULL splitPositions, uint8_t cascades, float near, float far, float lambda); }; @@ -953,19 +967,9 @@ class UTILS_PUBLIC LightManager : public FilamentAPI { */ bool isShadowCaster(Instance i) const noexcept; - /** - * Helper to process all components with a given function - * @tparam F a void(Entity entity, Instance instance) - * @param func a function of type F - */ - template - void forEachComponent(F func) noexcept { - utils::Entity const* const pEntity = getEntities(); - for (size_t i = 0, c = getComponentCount(); i < c; i++) { - // Instance 0 is the invalid instance - func(pEntity[i], Instance(i + 1)); - } - } +protected: + // prevent heap allocation + ~LightManager() = default; }; } // namespace filament diff --git a/filament/include/filament/Material.h b/filament/include/filament/Material.h index 123d8599f89..b4b9bbed3e2 100644 --- a/filament/include/filament/Material.h +++ b/filament/include/filament/Material.h @@ -30,7 +30,12 @@ #include +#include +#include + +#include #include +#include namespace utils { class CString; @@ -50,11 +55,11 @@ class UTILS_PUBLIC Material : public FilamentAPI { struct BuilderDetails; public: - using BlendingMode = BlendingMode; - using Shading = Shading; - using Interpolation = Interpolation; - using VertexDomain = VertexDomain; - using TransparencyMode = TransparencyMode; + using BlendingMode = filament::BlendingMode; + using Shading = filament::Shading; + using Interpolation = filament::Interpolation; + using VertexDomain = filament::VertexDomain; + using TransparencyMode = filament::TransparencyMode; using ParameterType = backend::UniformType; using Precision = backend::Precision; @@ -69,7 +74,7 @@ class UTILS_PUBLIC Material : public FilamentAPI { */ struct ParameterInfo { //! Name of the parameter. - const char* name; + const char* UTILS_NONNULL name; //! Whether the parameter is a sampler (texture). bool isSampler; //! Whether the parameter is a subpass type. @@ -105,7 +110,7 @@ class UTILS_PUBLIC Material : public FilamentAPI { * @param payload Pointer to the material data, must stay valid until build() is called. * @param size Size of the material data pointed to by "payload" in bytes. */ - Builder& package(const void* payload, size_t size); + Builder& package(const void* UTILS_NONNULL payload, size_t size); template using is_supported_constant_parameter_t = typename std::enable_if< @@ -127,11 +132,11 @@ class UTILS_PUBLIC Material : public FilamentAPI { * in the material definition. */ template> - Builder& constant(const char* name, size_t nameLength, T value); + Builder& constant(const char* UTILS_NONNULL name, size_t nameLength, T value); /** inline helper to provide the constant name as a null-terminated C string */ template> - inline Builder& constant(const char* name, T value) { + inline Builder& constant(const char* UTILS_NONNULL name, T value) { return constant(name, strlen(name), value); } @@ -147,7 +152,7 @@ class UTILS_PUBLIC Material : public FilamentAPI { * memory or other resources. * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. */ - Material* build(Engine& engine); + Material* UTILS_NULLABLE build(Engine& engine); private: friend class FMaterial; }; @@ -193,22 +198,22 @@ class UTILS_PUBLIC Material : public FilamentAPI { */ void compile(CompilerPriorityQueue priority, UserVariantFilterMask variants, - backend::CallbackHandler* handler = nullptr, - utils::Invocable&& callback = {}) noexcept; + backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, + utils::Invocable&& callback = {}) noexcept; inline void compile(CompilerPriorityQueue priority, UserVariantFilterBit variants, - backend::CallbackHandler* handler = nullptr, - utils::Invocable&& callback = {}) noexcept { + backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, + utils::Invocable&& callback = {}) noexcept { compile(priority, UserVariantFilterMask(variants), handler, - std::forward>(callback)); + std::forward>(callback)); } inline void compile(CompilerPriorityQueue priority, - backend::CallbackHandler* handler = nullptr, - utils::Invocable&& callback = {}) noexcept { + backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, + utils::Invocable&& callback = {}) noexcept { compile(priority, UserVariantFilterBit::ALL, handler, - std::forward>(callback)); + std::forward>(callback)); } /** @@ -220,13 +225,13 @@ class UTILS_PUBLIC Material : public FilamentAPI { * * @return A pointer to the new instance. */ - MaterialInstance* createInstance(const char* name = nullptr) const noexcept; + MaterialInstance* UTILS_NONNULL createInstance(const char* UTILS_NULLABLE name = nullptr) const noexcept; //! Returns the name of this material as a null-terminated string. - const char* getName() const noexcept; + const char* UTILS_NONNULL getName() const noexcept; //! Returns the shading model of this material. - Shading getShading() const noexcept; + Shading getShading() const noexcept; //! Returns the interpolation mode of this material. This affects how variables are interpolated. Interpolation getInterpolation() const noexcept; @@ -294,6 +299,9 @@ class UTILS_PUBLIC Material : public FilamentAPI { //! Returns the reflection mode used by this material. ReflectionMode getReflectionMode() const noexcept; + //! Returns the minimum required feature level for this material. + backend::FeatureLevel getFeatureLevel() const noexcept; + /** * Returns the number of parameters declared by this material. * The returned value can be 0. @@ -309,13 +317,13 @@ class UTILS_PUBLIC Material : public FilamentAPI { * * @return The number of parameters written to the parameters pointer. */ - size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept; + size_t getParameters(ParameterInfo* UTILS_NONNULL parameters, size_t count) const noexcept; //! Indicates whether a parameter of the given name exists on this material. - bool hasParameter(const char* name) const noexcept; + bool hasParameter(const char* UTILS_NONNULL name) const noexcept; //! Indicates whether an existing parameter is a sampler or not. - bool isSampler(const char* name) const noexcept; + bool isSampler(const char* UTILS_NONNULL name) const noexcept; /** * Sets the value of the given parameter on this material's default instance. @@ -326,7 +334,7 @@ class UTILS_PUBLIC Material : public FilamentAPI { * @see getDefaultInstance() */ template - void setDefaultParameter(const char* name, T value) noexcept { + void setDefaultParameter(const char* UTILS_NONNULL name, T value) noexcept { getDefaultInstance()->setParameter(name, value); } @@ -339,8 +347,8 @@ class UTILS_PUBLIC Material : public FilamentAPI { * * @see getDefaultInstance() */ - void setDefaultParameter(const char* name, Texture const* texture, - TextureSampler const& sampler) noexcept { + void setDefaultParameter(const char* UTILS_NONNULL name, + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) noexcept { getDefaultInstance()->setParameter(name, texture, sampler); } @@ -353,7 +361,7 @@ class UTILS_PUBLIC Material : public FilamentAPI { * * @see getDefaultInstance() */ - void setDefaultParameter(const char* name, RgbType type, math::float3 color) noexcept { + void setDefaultParameter(const char* UTILS_NONNULL name, RgbType type, math::float3 color) noexcept { getDefaultInstance()->setParameter(name, type, color); } @@ -366,15 +374,19 @@ class UTILS_PUBLIC Material : public FilamentAPI { * * @see getDefaultInstance() */ - void setDefaultParameter(const char* name, RgbaType type, math::float4 color) noexcept { + void setDefaultParameter(const char* UTILS_NONNULL name, RgbaType type, math::float4 color) noexcept { getDefaultInstance()->setParameter(name, type, color); } //! Returns this material's default instance. - MaterialInstance* getDefaultInstance() noexcept; + MaterialInstance* UTILS_NONNULL getDefaultInstance() noexcept; //! Returns this material's default instance. - MaterialInstance const* getDefaultInstance() const noexcept; + MaterialInstance const* UTILS_NONNULL getDefaultInstance() const noexcept; + +protected: + // prevent heap allocation + ~Material() = default; }; } // namespace filament diff --git a/filament/include/filament/MaterialInstance.h b/filament/include/filament/MaterialInstance.h index ee7a8e252ff..a0edd13554d 100644 --- a/filament/include/filament/MaterialInstance.h +++ b/filament/include/filament/MaterialInstance.h @@ -28,6 +28,12 @@ #include +#include + +#include +#include +#include + namespace filament { class Material; @@ -41,7 +47,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { using StringLiteralHelper = const char[N]; struct StringLiteral { - const char* data; + const char* UTILS_NONNULL data; size_t size; template StringLiteral(StringLiteralHelper const& s) noexcept // NOLINT(google-explicit-constructor) @@ -89,17 +95,18 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @param name A name for the new MaterialInstance or nullptr to use the template's name * @return A new MaterialInstance */ - static MaterialInstance* duplicate(MaterialInstance const* other, const char* name = nullptr) noexcept; + static MaterialInstance* UTILS_NONNULL duplicate(MaterialInstance const* UTILS_NONNULL other, + const char* UTILS_NULLABLE name = nullptr) noexcept; /** * @return the Material associated with this instance */ - Material const* getMaterial() const noexcept; + Material const* UTILS_NONNULL getMaterial() const noexcept; /** * @return the name associated with this instance */ - const char* getName() const noexcept; + const char* UTILS_NONNULL getName() const noexcept; /** * Set a uniform by name @@ -110,7 +117,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. */ template> - void setParameter(const char* name, size_t nameLength, T const& value); + void setParameter(const char* UTILS_NONNULL name, size_t nameLength, T const& value); /** inline helper to provide the name as a null-terminated string literal */ template> @@ -120,7 +127,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** inline helper to provide the name as a null-terminated C string */ template> - inline void setParameter(const char* name, T const& value) { + inline void setParameter(const char* UTILS_NONNULL name, T const& value) { setParameter(name, strlen(name), value); } @@ -135,17 +142,19 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. */ template> - void setParameter(const char* name, size_t nameLength, const T* values, size_t count); + void setParameter(const char* UTILS_NONNULL name, size_t nameLength, + const T* UTILS_NONNULL values, size_t count); /** inline helper to provide the name as a null-terminated string literal */ template> - inline void setParameter(StringLiteral name, const T* values, size_t count) { + inline void setParameter(StringLiteral name, const T* UTILS_NONNULL values, size_t count) { setParameter(name.data, name.size, values, count); } /** inline helper to provide the name as a null-terminated C string */ template> - inline void setParameter(const char* name, const T* values, size_t count) { + inline void setParameter(const char* UTILS_NONNULL name, + const T* UTILS_NONNULL values, size_t count) { setParameter(name, strlen(name), values, count); } @@ -162,18 +171,18 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @param sampler Sampler parameters. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. */ - void setParameter(const char* name, size_t nameLength, - Texture const* texture, TextureSampler const& sampler); + void setParameter(const char* UTILS_NONNULL name, size_t nameLength, + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler); /** inline helper to provide the name as a null-terminated string literal */ inline void setParameter(StringLiteral name, - Texture const* texture, TextureSampler const& sampler) { + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { setParameter(name.data, name.size, texture, sampler); } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* name, - Texture const* texture, TextureSampler const& sampler) { + inline void setParameter(const char* UTILS_NONNULL name, + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { setParameter(name, strlen(name), texture, sampler); } @@ -188,7 +197,8 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @param color Array of read, green, blue channels values. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. */ - void setParameter(const char* name, size_t nameLength, RgbType type, math::float3 color); + void setParameter(const char* UTILS_NONNULL name, size_t nameLength, + RgbType type, math::float3 color); /** inline helper to provide the name as a null-terminated string literal */ inline void setParameter(StringLiteral name, RgbType type, math::float3 color) { @@ -196,7 +206,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* name, RgbType type, math::float3 color) { + inline void setParameter(const char* UTILS_NONNULL name, RgbType type, math::float3 color) { setParameter(name, strlen(name), type, color); } @@ -211,7 +221,8 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @param color Array of read, green, blue and alpha channels values. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. */ - void setParameter(const char* name, size_t nameLength, RgbaType type, math::float4 color); + void setParameter(const char* UTILS_NONNULL name, size_t nameLength, + RgbaType type, math::float4 color); /** inline helper to provide the name as a null-terminated string literal */ inline void setParameter(StringLiteral name, RgbaType type, math::float4 color) { @@ -219,7 +230,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* name, RgbaType type, math::float4 color) { + inline void setParameter(const char* UTILS_NONNULL name, RgbaType type, math::float4 color) { setParameter(name, strlen(name), type, color); } @@ -479,6 +490,10 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { */ void setStencilWriteMask(uint8_t writeMask, StencilFace face = StencilFace::FRONT_AND_BACK) noexcept; + +protected: + // prevent heap allocation + ~MaterialInstance() = default; }; } // namespace filament diff --git a/filament/include/filament/MorphTargetBuffer.h b/filament/include/filament/MorphTargetBuffer.h index d080d0da674..655bb8d848d 100644 --- a/filament/include/filament/MorphTargetBuffer.h +++ b/filament/include/filament/MorphTargetBuffer.h @@ -21,8 +21,11 @@ #include +#include + #include +#include namespace filament { @@ -65,14 +68,13 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this MorphTargetBuffer with. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object. * * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of * memory or other resources. * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. */ - MorphTargetBuffer* build(Engine& engine); + MorphTargetBuffer* UTILS_NONNULL build(Engine& engine); private: friend class FMorphTargetBuffer; }; @@ -92,7 +94,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { * @see setTangentsAt */ void setPositionsAt(Engine& engine, size_t targetIndex, - math::float3 const* positions, size_t count, size_t offset = 0); + math::float3 const* UTILS_NONNULL positions, size_t count, size_t offset = 0); /** * Updates positions for the given morph target. @@ -107,7 +109,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { * @see setTangentsAt */ void setPositionsAt(Engine& engine, size_t targetIndex, - math::float4 const* positions, size_t count, size_t offset = 0); + math::float4 const* UTILS_NONNULL positions, size_t count, size_t offset = 0); /** * Updates tangents for the given morph target. @@ -123,7 +125,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { * @see setPositionsAt */ void setTangentsAt(Engine& engine, size_t targetIndex, - math::short4 const* tangents, size_t count, size_t offset = 0); + math::short4 const* UTILS_NONNULL tangents, size_t count, size_t offset = 0); /** * Returns the vertex count of this MorphTargetBuffer. @@ -136,6 +138,10 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { * @return The number of targets the MorphTargetBuffer holds. */ size_t getCount() const noexcept; + +protected: + // prevent heap allocation + ~MorphTargetBuffer() = default; }; } // namespace filament diff --git a/filament/include/filament/Options.h b/filament/include/filament/Options.h index f09fd4dac36..3966053ed20 100644 --- a/filament/include/filament/Options.h +++ b/filament/include/filament/Options.h @@ -19,10 +19,13 @@ #include -#include +#include +#include #include +#include + namespace filament { class Texture; @@ -293,6 +296,7 @@ struct DepthOfFieldOptions { MEDIAN }; float cocScale = 1.0f; //!< circle of confusion scale factor (amount of blur) + float cocAspectRatio = 1.0f; //!< width/height aspect ratio of the circle of confusion (simulate anamorphic lenses) float maxApertureDiameter = 0.01f; //!< maximum aperture diameter in meters (zero to disable rotation) bool enabled = false; //!< enable or disable depth of field effect Filter filter = Filter::MEDIAN; //!< filter to use for filling gaps in the kernel @@ -401,7 +405,7 @@ struct AmbientOcclusionOptions { }; /** - * Options for Temporal Multi-Sample Anti-aliasing (MSAA) + * Options for Multi-Sample Anti-aliasing (MSAA) * @see setMultiSampleAntiAliasingOptions() */ struct MultiSampleAntiAliasingOptions { @@ -424,12 +428,53 @@ struct MultiSampleAntiAliasingOptions { /** * Options for Temporal Anti-aliasing (TAA) + * Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process + * shaders to be recompiled. These options should be changed or set during initialization. + * `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time. + * + * `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state. + * see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information. + * * @see setTemporalAntiAliasingOptions() */ struct TemporalAntiAliasingOptions { - float filterWidth = 1.0f; //!< reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother) - float feedback = 0.04f; //!< history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). + float filterWidth = 1.0f; //!< reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother) + float feedback = 0.12f; //!< history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). + float lodBias = -1.0f; //!< texturing lod bias (typically -1 or -2) + float sharpness = 0.0f; //!< post-TAA sharpen, especially useful when upscaling is true. bool enabled = false; //!< enables or disables temporal anti-aliasing + bool upscaling = false; //!< 4x TAA upscaling. Disables Dynamic Resolution. [BETA] + + enum class BoxType : uint8_t { + AABB, //!< use an AABB neighborhood + VARIANCE, //!< use the variance of the neighborhood (not recommended) + AABB_VARIANCE //!< use both AABB and variance + }; + + enum class BoxClipping : uint8_t { + ACCURATE, //!< Accurate box clipping + CLAMP, //!< clamping + NONE //!< no rejections (use for debugging) + }; + + enum class JitterPattern : uint8_t { + RGSS_X4, //! 4-samples, rotated grid sampling + UNIFORM_HELIX_X4, //! 4-samples, uniform grid in helix sequence + HALTON_23_X8, //! 8-samples of halton 2,3 + HALTON_23_X16, //! 16-samples of halton 2,3 + HALTON_23_X32 //! 32-samples of halton 2,3 + }; + + bool filterHistory = true; //!< whether to filter the history buffer + bool filterInput = true; //!< whether to apply the reconstruction filter to the input + bool useYCoCg = false; //!< whether to use the YcoCg color-space for history rejection + BoxType boxType = BoxType::AABB; //!< type of color gamut box + BoxClipping boxClipping = BoxClipping::ACCURATE; //!< clipping algorithm + JitterPattern jitterPattern = JitterPattern::HALTON_23_X16; //! Jitter Pattern + float varianceGamma = 1.0f; //! High values increases ghosting artefact, lower values increases jittering, range [0.75, 1.25] + + bool preventFlickering = false; //!< adjust the feedback dynamically to reduce flickering + bool historyReprojection = true; //!< whether to apply history reprojection (debug option) }; /** diff --git a/filament/include/filament/RenderTarget.h b/filament/include/filament/RenderTarget.h index 508e1c246f2..fc76111da74 100644 --- a/filament/include/filament/RenderTarget.h +++ b/filament/include/filament/RenderTarget.h @@ -24,7 +24,10 @@ #include #include +#include + #include +#include namespace filament { @@ -103,7 +106,7 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { * @param texture The associated texture object. * @return A reference to this Builder for chaining calls. */ - Builder& texture(AttachmentPoint attachment, Texture* texture) noexcept; + Builder& texture(AttachmentPoint attachment, Texture* UTILS_NULLABLE texture) noexcept; /** * Sets the mipmap level for a given attachment point. @@ -135,10 +138,9 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { /** * Creates the RenderTarget object and returns a pointer to it. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object. */ - RenderTarget* build(Engine& engine); + RenderTarget* UTILS_NONNULL build(Engine& engine); private: friend class FRenderTarget; @@ -149,7 +151,7 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { * @param attachment Attachment point * @return A Texture object or nullptr if no texture is set for this attachment point */ - Texture* getTexture(AttachmentPoint attachment) const noexcept; + Texture* UTILS_NULLABLE getTexture(AttachmentPoint attachment) const noexcept; /** * Returns the mipmap level set on the given attachment point @@ -180,6 +182,10 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { * @return Number of color attachments usable in a render target. */ uint8_t getSupportedColorAttachmentsCount() const noexcept; + +protected: + // prevent heap allocation + ~RenderTarget() = default; }; } // namespace filament diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 30705b8d62f..bb50b7d1db8 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -29,9 +29,15 @@ #include #include +#include +#include #include +#include +#include +#include + namespace utils { class Entity; } // namespace utils @@ -102,6 +108,29 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { */ Instance getInstance(utils::Entity e) const noexcept; + /** + * @return the number of Components + */ + size_t getComponentCount() const noexcept; + + /** + * @return true if the this manager has no components + */ + bool empty() const noexcept; + + /** + * Retrieve the `Entity` of the component from its `Instance`. + * @param i Instance of the component obtained from getInstance() + * @return + */ + utils::Entity getEntity(Instance i) const noexcept; + + /** + * Retrieve the Entities of all the components of this manager. + * @return A list, in no particular order, of all the entities managed by this manager. + */ + utils::Entity const* UTILS_NONNULL getEntities() const noexcept; + /** * The transformation associated with a skinning joint. * @@ -127,6 +156,15 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { */ static constexpr uint8_t DEFAULT_CHANNEL = 2u; + /** + * Type of geometry for a Renderable + */ + enum class GeometryType : uint8_t { + DYNAMIC, //!< dynamic gemoetry has no restriction + STATIC_BOUNDS, //!< bounds and world space transform are immutable + STATIC //!< skinning/morphing not allowed and Vertex/IndexBuffer immutables + }; + /** * Creates a builder for renderable components. * @@ -161,9 +199,30 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @param maxIndex specifies the maximum index contained in the index buffer * @param count number of indices to read (for triangles, this should be a multiple of 3) */ - Builder& geometry(size_t index, PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices, size_t offset, size_t minIndex, size_t maxIndex, size_t count) noexcept; - Builder& geometry(size_t index, PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices, size_t offset, size_t count) noexcept; //!< \overload - Builder& geometry(size_t index, PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices) noexcept; //!< \overload + Builder& geometry(size_t index, PrimitiveType type, + VertexBuffer* UTILS_NONNULL vertices, + IndexBuffer* UTILS_NONNULL indices, + size_t offset, size_t minIndex, size_t maxIndex, size_t count) noexcept; + + Builder& geometry(size_t index, PrimitiveType type, + VertexBuffer* UTILS_NONNULL vertices, + IndexBuffer* UTILS_NONNULL indices, + size_t offset, size_t count) noexcept; //!< \overload + + Builder& geometry(size_t index, PrimitiveType type, + VertexBuffer* UTILS_NONNULL vertices, + IndexBuffer* UTILS_NONNULL indices) noexcept; //!< \overload + + + /** + * Specify the type of geometry for this renderable. DYNAMIC geometry has no restriction, + * STATIC_BOUNDS geometry means that both the bounds and the world-space transform of the + * the renderable are immutable. + * STATIC geometry has the same restrictions as STATIC_BOUNDS, but in addition disallows + * skinning, morphing and changing the VertexBuffer or IndexBuffer in any way. + * @param enable whether this renderable has static bounds. false by default. + */ + Builder& geometryType(GeometryType type) noexcept; /** * Binds a material instance to the specified primitive. @@ -180,7 +239,8 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * * @see Engine::setActiveFeatureLevel */ - Builder& material(size_t index, MaterialInstance const* materialInstance) noexcept; + Builder& material(size_t index, + MaterialInstance const* UTILS_NONNULL materialInstance) noexcept; /** * The axis-aligned bounding box of the renderable. @@ -328,7 +388,8 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @param count 0 to disable, otherwise the number of bone transforms (up to 255) * @param offset offset in the SkinningBuffer */ - Builder& skinning(SkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept; + Builder& skinning(SkinningBuffer* UTILS_NONNULL skinningBuffer, + size_t count, size_t offset) noexcept; /** @@ -346,8 +407,8 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @param boneCount 0 to disable, otherwise the number of bone transforms (up to 255) * @param transforms the initial set of transforms (one for each bone) */ - Builder& skinning(size_t boneCount, math::mat4f const* transforms) noexcept; - Builder& skinning(size_t boneCount, Bone const* bones) noexcept; //!< \overload + Builder& skinning(size_t boneCount, math::mat4f const* UTILS_NONNULL transforms) noexcept; + Builder& skinning(size_t boneCount, Bone const* UTILS_NONNULL bones) noexcept; //!< \overload Builder& skinning(size_t boneCount) noexcept; //!< \overload /** @@ -376,7 +437,8 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @see VertexBuffer:Builder:advancedSkinning */ Builder& boneIndicesAndWeights(size_t primitiveIndex, - math::float2 const* indicesAndWeights, size_t count, size_t bonesPerVertex) noexcept; + math::float2 const* UTILS_NONNULL indicesAndWeights, + size_t count, size_t bonesPerVertex) noexcept; /** * Define bone indices and weights "pairs" for vertex skinning as a float2. @@ -439,10 +501,11 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3) */ Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept; + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, + size_t offset, size_t count) noexcept; inline Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* morphTargetBuffer) noexcept; + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept; /** * Sets the drawing order for blended primitives. The drawing order is either global or @@ -509,7 +572,8 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * the result of Engine::getMaxAutomaticInstances(). * @param instanceBuffer an InstanceBuffer containing at least instanceCount transforms */ - Builder& instances(size_t instanceCount, InstanceBuffer* instanceBuffer) noexcept; + Builder& instances(size_t instanceCount, + InstanceBuffer* UTILS_NONNULL instanceBuffer) noexcept; /** * Adds the Renderable component to an entity. @@ -536,18 +600,16 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { friend class FRenderPrimitive; friend class FRenderableManager; struct Entry { - VertexBuffer* vertices = nullptr; - IndexBuffer* indices = nullptr; + VertexBuffer* UTILS_NULLABLE vertices = nullptr; + IndexBuffer* UTILS_NULLABLE indices = nullptr; size_t offset = 0; - size_t minIndex = 0; - size_t maxIndex = 0; size_t count = 0; - MaterialInstance const* materialInstance = nullptr; + MaterialInstance const* UTILS_NULLABLE materialInstance = nullptr; PrimitiveType type = PrimitiveType::TRIANGLES; uint16_t blendOrder = 0; bool globalBlendOrderEnabled = false; struct { - MorphTargetBuffer* buffer = nullptr; + MorphTargetBuffer* UTILS_NULLABLE buffer = nullptr; size_t offset = 0; size_t count = 0; } morphing; @@ -561,11 +623,12 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { /** * Changes the bounding box used for frustum culling. + * The renderable must not have staticGeometry enabled. * * \see Builder::boundingBox() * \see RenderableManager::getAxisAlignedBoundingBox() */ - void setAxisAlignedBoundingBox(Instance instance, const Box& aabb) noexcept; + void setAxisAlignedBoundingBox(Instance instance, const Box& aabb); /** * Changes the visibility bits. @@ -665,8 +728,11 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * Updates the bone transforms in the range [offset, offset + boneCount). * The bones must be pre-allocated using Builder::skinning(). */ - void setBones(Instance instance, Bone const* transforms, size_t boneCount = 1, size_t offset = 0); - void setBones(Instance instance, math::mat4f const* transforms, size_t boneCount = 1, size_t offset = 0); //!< \overload + void setBones(Instance instance, Bone const* UTILS_NONNULL transforms, + size_t boneCount = 1, size_t offset = 0); + + void setBones(Instance instance, math::mat4f const* UTILS_NONNULL transforms, + size_t boneCount = 1, size_t offset = 0); //!< \overload /** * Associates a region of a SkinningBuffer to a renderable instance @@ -679,7 +745,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @param count Size of the region in bones, must be smaller or equal to 256. * @param offset Start offset of the region in bones */ - void setSkinningBuffer(Instance instance, SkinningBuffer* skinningBuffer, + void setSkinningBuffer(Instance instance, SkinningBuffer* UTILS_NONNULL skinningBuffer, size_t count, size_t offset); /** @@ -694,24 +760,25 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @param offset Index of the first morph target weight to set at instance. */ void setMorphWeights(Instance instance, - float const* weights, size_t count, size_t offset = 0); + float const* UTILS_NONNULL weights, size_t count, size_t offset = 0); /** * Associates a MorphTargetBuffer to the given primitive. */ void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count); + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count); /** * Utility method to change a MorphTargetBuffer to the given primitive */ inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* morphTargetBuffer); + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer); /** * Get a MorphTargetBuffer to the given primitive or null if it doesn't exist. */ - MorphTargetBuffer* getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept; + MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBufferAt(Instance instance, + uint8_t level, size_t primitiveIndex) const noexcept; /** * Gets the number of morphing in the given entity. @@ -753,20 +820,22 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * @see Engine::setActiveFeatureLevel */ void setMaterialInstanceAt(Instance instance, - size_t primitiveIndex, MaterialInstance const* materialInstance); + size_t primitiveIndex, MaterialInstance const* UTILS_NONNULL materialInstance); /** * Retrieves the material instance that is bound to the given primitive. */ - MaterialInstance* getMaterialInstanceAt(Instance instance, size_t primitiveIndex) const noexcept; + MaterialInstance* UTILS_NULLABLE getMaterialInstanceAt( + Instance instance, size_t primitiveIndex) const noexcept; /** * Changes the geometry for the given primitive. * * \see Builder::geometry() */ - void setGeometryAt(Instance instance, size_t primitiveIndex, - PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices, + void setGeometryAt(Instance instance, size_t primitiveIndex, PrimitiveType type, + VertexBuffer* UTILS_NONNULL vertices, + IndexBuffer* UTILS_NONNULL indices, size_t offset, size_t count) noexcept; /** @@ -827,27 +896,37 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { template::type, typename = typename is_supported_index_type::type> - static Box computeAABB(VECTOR const* vertices, INDEX const* indices, size_t count, + static Box computeAABB( + VECTOR const* UTILS_NONNULL vertices, + INDEX const* UTILS_NONNULL indices, size_t count, size_t stride = sizeof(VECTOR)) noexcept; + +protected: + // prevent heap allocation + ~RenderableManager() = default; }; -RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* morphTargetBuffer) noexcept { +RenderableManager::Builder& RenderableManager::Builder::morphing( + uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { return morphing(level, primitiveIndex, morphTargetBuffer, 0, morphTargetBuffer->getVertexCount()); } -void RenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* morphTargetBuffer) { +void RenderableManager::setMorphTargetBufferAt( + Instance instance, uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) { setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0, morphTargetBuffer->getVertexCount()); } template -Box RenderableManager::computeAABB(VECTOR const* vertices, INDEX const* indices, size_t count, - size_t stride) noexcept { - math::float3 bmin(std::numeric_limits::max()); - math::float3 bmax(std::numeric_limits::lowest()); +Box RenderableManager::computeAABB( + VECTOR const* UTILS_NONNULL vertices, + INDEX const* UTILS_NONNULL indices, + size_t count, size_t stride) noexcept { + math::float3 bmin(FLT_MAX); + math::float3 bmax(-FLT_MAX); for (size_t i = 0; i < count; ++i) { VECTOR const* p = reinterpret_cast( (char const*)vertices + indices[i] * stride); diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h index 999f31a9652..fdc291b1fd1 100644 --- a/filament/include/filament/Renderer.h +++ b/filament/include/filament/Renderer.h @@ -23,9 +23,6 @@ #include -#include -#include - #include #include @@ -184,14 +181,14 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * * @return A pointer to the Engine instance this Renderer is associated to. */ - Engine* getEngine() noexcept; + Engine* UTILS_NONNULL getEngine() noexcept; /** * Get the Engine that created this Renderer. * * @return A constant pointer to the Engine instance this Renderer is associated to. */ - inline Engine const* getEngine() const noexcept { + inline Engine const* UTILS_NONNULL getEngine() const noexcept { return const_cast(this)->getEngine(); } @@ -267,7 +264,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * @see * endFrame() */ - bool beginFrame(SwapChain* swapChain, + bool beginFrame(SwapChain* UTILS_NONNULL swapChain, uint64_t vsyncSteadyClockTimeNano = 0u); /** @@ -338,7 +335,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * beginFrame(), endFrame(), View * */ - void render(View const* view); + void render(View const* UTILS_NONNULL view); /** * Copy the currently rendered view to the indicated swap chain, using the @@ -353,7 +350,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * copyFrame() should be called after a frame is rendered using render() * but before endFrame() is called. */ - void copyFrame(SwapChain* dstSwapChain, Viewport const& dstViewport, + void copyFrame(SwapChain* UTILS_NONNULL dstSwapChain, Viewport const& dstViewport, Viewport const& srcViewport, uint32_t flags = 0); /** @@ -493,7 +490,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * readPixels() is intended for debugging and testing. It will impact performance significantly. * */ - void readPixels(RenderTarget* renderTarget, + void readPixels(RenderTarget* UTILS_NONNULL renderTarget, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& buffer); @@ -520,7 +517,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * However, internally, renderStandaloneView() is highly multi-threaded to both improve * performance in mitigate the call's latency. */ - void renderStandaloneView(View const* view); + void renderStandaloneView(View const* UTILS_NONNULL view); /** @@ -579,6 +576,10 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * getUserTime() */ void resetUserTime(); + +protected: + // prevent heap allocation + ~Renderer() = default; }; } // namespace filament diff --git a/filament/include/filament/Scene.h b/filament/include/filament/Scene.h index 3e6a63c4b3f..9df6285c4aa 100644 --- a/filament/include/filament/Scene.h +++ b/filament/include/filament/Scene.h @@ -24,6 +24,8 @@ #include #include +#include + namespace utils { class Entity; } // namespace utils @@ -73,14 +75,14 @@ class UTILS_PUBLIC Scene : public FilamentAPI { * * @param skybox The Skybox to use to fill untouched pixels, or nullptr to unset the Skybox. */ - void setSkybox(Skybox* skybox) noexcept; + void setSkybox(Skybox* UTILS_NULLABLE skybox) noexcept; /** * Returns the Skybox associated with the Scene. * * @return The associated Skybox, or nullptr if there is none. */ - Skybox* getSkybox() const noexcept; + Skybox* UTILS_NULLABLE getSkybox() const noexcept; /** * Set the IndirectLight to use when rendering the Scene. @@ -91,7 +93,7 @@ class UTILS_PUBLIC Scene : public FilamentAPI { * @param ibl The IndirectLight to use when rendering the Scene or nullptr to unset. * @see getIndirectLight */ - void setIndirectLight(IndirectLight* ibl) noexcept; + void setIndirectLight(IndirectLight* UTILS_NULLABLE ibl) noexcept; /** * Get the IndirectLight or nullptr if none is set. @@ -99,7 +101,7 @@ class UTILS_PUBLIC Scene : public FilamentAPI { * @return the the IndirectLight or nullptr if none is set * @see setIndirectLight */ - IndirectLight* getIndirectLight() const noexcept; + IndirectLight* UTILS_NULLABLE getIndirectLight() const noexcept; /** * Adds an Entity to the Scene. @@ -118,7 +120,7 @@ class UTILS_PUBLIC Scene : public FilamentAPI { * @param entities Array containing entities to add to the scene. * @param count Size of the entity array. */ - void addEntities(const utils::Entity* entities, size_t count); + void addEntities(const utils::Entity* UTILS_NONNULL entities, size_t count); /** * Removes the Renderable from the Scene. @@ -137,19 +139,25 @@ class UTILS_PUBLIC Scene : public FilamentAPI { * @param entities Array containing entities to remove from the scene. * @param count Size of the entity array. */ - void removeEntities(const utils::Entity* entities, size_t count); + void removeEntities(const utils::Entity* UTILS_NONNULL entities, size_t count); /** - * Returns the number of Renderable objects in the Scene. + * Returns the total number of Entities in the Scene, whether alive or not. + * @return Total number of Entities in the Scene. + */ + size_t getEntityCount() const noexcept; + + /** + * Returns the number of active (alive) Renderable objects in the Scene. * - * @return number of Renderable objects in the Scene. + * @return The number of active (alive) Renderable objects in the Scene. */ size_t getRenderableCount() const noexcept; /** - * Returns the total number of Light objects in the Scene. + * Returns the number of active (alive) Light objects in the Scene. * - * @return The total number of Light objects in the Scene. + * @return The number of active (alive) Light objects in the Scene. */ size_t getLightCount() const noexcept; @@ -168,6 +176,10 @@ class UTILS_PUBLIC Scene : public FilamentAPI { * @param functor User provided functor called for each entity in the scene */ void forEach(utils::Invocable&& functor) const noexcept; + +protected: + // prevent heap allocation + ~Scene() = default; }; } // namespace filament diff --git a/filament/include/filament/SkinningBuffer.h b/filament/include/filament/SkinningBuffer.h index 007feb85085..36ae30ed438 100644 --- a/filament/include/filament/SkinningBuffer.h +++ b/filament/include/filament/SkinningBuffer.h @@ -26,7 +26,7 @@ #include #include - +#include namespace filament { @@ -74,8 +74,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this SkinningBuffer with. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object. * * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of * memory or other resources. @@ -83,7 +82,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { * * @see SkinningBuffer::setBones */ - SkinningBuffer* build(Engine& engine); + SkinningBuffer* UTILS_NONNULL build(Engine& engine); private: friend class FSkinningBuffer; }; @@ -96,7 +95,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { * @param offset offset in elements (not bytes) in the SkinningBuffer (not in transforms) * @see RenderableManager::setSkinningBuffer */ - void setBones(Engine& engine, RenderableManager::Bone const* transforms, + void setBones(Engine& engine, RenderableManager::Bone const* UTILS_NONNULL transforms, size_t count, size_t offset = 0); /** @@ -107,7 +106,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { * @param offset offset in elements (not bytes) in the SkinningBuffer (not in transforms) * @see RenderableManager::setSkinningBuffer */ - void setBones(Engine& engine, math::mat4f const* transforms, + void setBones(Engine& engine, math::mat4f const* UTILS_NONNULL transforms, size_t count, size_t offset = 0); /** @@ -115,6 +114,10 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { * @return The number of bones the SkinningBuffer holds. */ size_t getBoneCount() const noexcept; + +protected: + // prevent heap allocation + ~SkinningBuffer() = default; }; } // namespace filament diff --git a/filament/include/filament/Skybox.h b/filament/include/filament/Skybox.h index 5e9f2c60636..ce203aae6a1 100644 --- a/filament/include/filament/Skybox.h +++ b/filament/include/filament/Skybox.h @@ -23,9 +23,10 @@ #include -#include #include +#include + namespace filament { class FSkybox; @@ -91,7 +92,7 @@ class UTILS_PUBLIC Skybox : public FilamentAPI { * * @see Texture */ - Builder& environment(Texture* cubemap) noexcept; + Builder& environment(Texture* UTILS_NONNULL cubemap) noexcept; /** * Indicates whether the sun should be rendered. The sun can only be @@ -135,9 +136,9 @@ class UTILS_PUBLIC Skybox : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this Skybox with. * - * @return pointer to the newly created object, or nullptr if the light couldn't be created. + * @return pointer to the newly created object. */ - Skybox* build(Engine& engine); + Skybox* UTILS_NONNULL build(Engine& engine); private: friend class FSkybox; @@ -171,9 +172,13 @@ class UTILS_PUBLIC Skybox : public FilamentAPI { float getIntensity() const noexcept; /** - * @return the associated texture, or null if it does not exist + * @return the associated texture */ - Texture const* getTexture() const noexcept; + Texture const* UTILS_NONNULL getTexture() const noexcept; + +protected: + // prevent heap allocation + ~Skybox() = default; }; } // namespace filament diff --git a/filament/include/filament/Stream.h b/filament/include/filament/Stream.h index 7fccecfe3ee..6cafbacc8b3 100644 --- a/filament/include/filament/Stream.h +++ b/filament/include/filament/Stream.h @@ -20,12 +20,12 @@ #include #include - -#include #include #include +#include + namespace filament { class FStream; @@ -114,7 +114,7 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * * @return This Builder, for chaining calls. */ - Builder& stream(void* stream) noexcept; + Builder& stream(void* UTILS_NULLABLE stream) noexcept; /** * @@ -141,9 +141,9 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this Stream with. * - * @return pointer to the newly created object, or nullptr if the stream couldn't be created. + * @return pointer to the newly created object. */ - Stream* build(Engine& engine); + Stream* UTILS_NONNULL build(Engine& engine); private: friend class FStream; @@ -171,11 +171,12 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * * @param image Pointer to AHardwareBuffer, casted to void* since this is a public header. * @param callback This is triggered by Filament when it wishes to release the image. - * It callback tales two arguments: the AHardwareBuffer and the userdata. + * The callback tales two arguments: the AHardwareBuffer and the userdata. * @param userdata Optional closure data. Filament will pass this into the callback when it * releases the image. */ - void setAcquiredImage(void* image, Callback callback, void* userdata) noexcept; + void setAcquiredImage(void* UTILS_NONNULL image, + Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata) noexcept; /** * @see setAcquiredImage(void*, Callback, void*) @@ -187,7 +188,9 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * @param userdata Optional closure data. Filament will pass this into the callback when it * releases the image. */ - void setAcquiredImage(void* image, backend::CallbackHandler* handler, Callback callback, void* userdata) noexcept; + void setAcquiredImage(void* UTILS_NONNULL image, + backend::CallbackHandler* UTILS_NULLABLE handler, + Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata) noexcept; /** * Updates the size of the incoming stream. Whether this value is used is @@ -207,6 +210,10 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * @return timestamp in nanosecond. */ int64_t getTimestamp() const noexcept; + +protected: + // prevent heap allocation + ~Stream() = default; }; } // namespace filament diff --git a/filament/include/filament/SwapChain.h b/filament/include/filament/SwapChain.h index 26c3da4a04d..0af01afc966 100644 --- a/filament/include/filament/SwapChain.h +++ b/filament/include/filament/SwapChain.h @@ -21,11 +21,12 @@ #include #include -#include #include #include +#include + namespace filament { class Engine; @@ -151,7 +152,7 @@ class Engine; class UTILS_PUBLIC SwapChain : public FilamentAPI { public: using FrameScheduledCallback = backend::FrameScheduledCallback; - using FrameCompletedCallback = utils::Invocable; + using FrameCompletedCallback = utils::Invocable; /** * Requests a SwapChain with an alpha channel. @@ -224,18 +225,33 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * @see View.setStencilBufferEnabled * @see View.setPostProcessingEnabled */ - static constexpr uint64_t CONFIG_HAS_STENCIL_BUFFER = backend::SWAP_CHAIN_HAS_STENCIL_BUFFER; + static constexpr uint64_t CONFIG_HAS_STENCIL_BUFFER = backend::SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER; + + /** + * The SwapChain contains protected content. Only supported when isProtectedContentSupported() + * is true. + */ + static constexpr uint64_t CONFIG_PROTECTED_CONTENT = backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; + + /** + * Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag. + * The default implementation returns false. + * + * @param engine A pointer to the filament Engine + * @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise. + */ + static bool isProtectedContentSupported(Engine& engine) noexcept; /** - * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. + * Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag. * The default implementation returns false. * * @param engine A pointer to the filament Engine - * @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise. + * @return true if CONFIG_SRGB_COLORSPACE is supported, false otherwise. */ static bool isSRGBSwapChainSupported(Engine& engine) noexcept; - void* getNativeWindow() const noexcept; + void* UTILS_NULLABLE getNativeWindow() const noexcept; /** * FrameScheduledCallback is a callback function that notifies an application when Filament has @@ -252,6 +268,16 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * automatically schedule itself for presentation. Instead, the application must call the * PresentCallable passed to the FrameScheduledCallback. * + * There may be only one FrameScheduledCallback set per SwapChain. A call to + * SwapChain::setFrameScheduledCallback will overwrite any previous FrameScheduledCallbacks set + * on the same SwapChain. + * + * If your application delays the call to the PresentCallable by, for example, calling it on a + * separate thread, you must ensure all PresentCallables have been called before shutting down + * the Filament Engine. You can do this by issuing an Engine::flushAndWait before calling + * Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean + * up all memory related to frame presentation. + * * @param callback A callback, or nullptr to unset. * @param user An optional pointer to user data passed to the callback function. * @@ -262,7 +288,18 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * * @see PresentCallable */ - void setFrameScheduledCallback(FrameScheduledCallback callback, void* user = nullptr); + void setFrameScheduledCallback(FrameScheduledCallback UTILS_NULLABLE callback, + void* UTILS_NULLABLE user = nullptr); + + /** + * Returns the SwapChain::FrameScheduledCallback that was previously set with + * SwapChain::setFrameScheduledCallback, or nullptr if one is not set. + * + * @return the previously-set FrameScheduledCallback, or nullptr + * + * @see SwapChain::setFrameCompletedCallback + */ + UTILS_NULLABLE FrameScheduledCallback getFrameScheduledCallback() const noexcept; /** * FrameCompletedCallback is a callback function that notifies an application when a frame's @@ -283,9 +320,13 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * * @see CallbackHandler */ - void setFrameCompletedCallback(backend::CallbackHandler* handler = nullptr, + void setFrameCompletedCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, FrameCompletedCallback&& callback = {}) noexcept; + +protected: + // prevent heap allocation + ~SwapChain() = default; }; } // namespace filament diff --git a/filament/include/filament/Texture.h b/filament/include/filament/Texture.h index c005c2b165e..8a27f831c2b 100644 --- a/filament/include/filament/Texture.h +++ b/filament/include/filament/Texture.h @@ -20,12 +20,16 @@ #define TNT_FILAMENT_TEXTURE_H #include + #include #include #include +#include + #include +#include namespace filament { @@ -84,6 +88,9 @@ class UTILS_PUBLIC Texture : public FilamentAPI { /** @return whether a backend supports a particular format. */ static bool isTextureFormatSupported(Engine& engine, InternalFormat format) noexcept; + /** @return whether this backend supports protected textures. */ + static bool isProtectedTexturesSupported(Engine& engine) noexcept; + /** @return whether a backend supports texture swizzling. */ static bool isTextureSwizzleSupported(Engine& engine) noexcept; @@ -200,14 +207,13 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this Texture with. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object. * * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of * memory or other resources. * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. */ - Texture* build(Engine& engine); + Texture* UTILS_NONNULL build(Engine& engine); /* no user serviceable parts below */ @@ -396,7 +402,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @see Builder::sampler() * */ - void setExternalImage(Engine& engine, void* image) noexcept; + void setExternalImage(Engine& engine, void* UTILS_NONNULL image) noexcept; /** * Specify the external image and plane to associate with this Texture. Typically the external @@ -427,7 +433,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * kCVPixelFormatType_420YpCbCr8BiPlanarFullRange images. On platforms * other than iOS, this method is a no-op. */ - void setExternalImage(Engine& engine, void* image, size_t plane) noexcept; + void setExternalImage(Engine& engine, void* UTILS_NONNULL image, size_t plane) noexcept; /** * Specify the external stream to associate with this Texture. Typically the external @@ -446,16 +452,17 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @see Builder::sampler(), Stream * */ - void setExternalStream(Engine& engine, Stream* stream) noexcept; + void setExternalStream(Engine& engine, Stream* UTILS_NULLABLE stream) noexcept; /** * Generates all the mipmap levels automatically. This requires the texture to have a - * color-renderable format. + * color-renderable format and usage set to BLIT_SRC | BLIT_DST. If unspecified, + * usage bits are set automatically. * * @param engine Engine this texture is associated to. * * @attention \p engine must be the instance passed to Builder::build() - * @attention This Texture instance must NOT use Sampler::SAMPLER_CUBEMAP or it has no effect + * @attention This Texture instance must NOT use SamplerType::SAMPLER_3D or it has no effect */ void generateMipmaps(Engine& engine) const noexcept; @@ -495,7 +502,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { */ void generatePrefilterMipmap(Engine& engine, PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets, - PrefilterOptions const* options = nullptr); + PrefilterOptions const* UTILS_NULLABLE options = nullptr); /** @deprecated */ @@ -541,6 +548,10 @@ class UTILS_PUBLIC Texture : public FilamentAPI { return *this; } }; + +protected: + // prevent heap allocation + ~Texture() = default; }; } // namespace filament diff --git a/filament/include/filament/TextureSampler.h b/filament/include/filament/TextureSampler.h index 77ee4fab34a..ba5e5534857 100644 --- a/filament/include/filament/TextureSampler.h +++ b/filament/include/filament/TextureSampler.h @@ -25,6 +25,8 @@ #include +#include + namespace filament { /** diff --git a/filament/include/filament/ToneMapper.h b/filament/include/filament/ToneMapper.h index d220f47b662..e89e702c99d 100644 --- a/filament/include/filament/ToneMapper.h +++ b/filament/include/filament/ToneMapper.h @@ -21,6 +21,8 @@ #include +#include + namespace filament { /** @@ -37,10 +39,12 @@ namespace filament { * * - Configurable tone mapping operators * - GenericToneMapper + * - AgXToneMapper * - Fixed-aesthetic tone mapping operators * - ACESToneMapper * - ACESLegacyToneMapper * - FilmicToneMapper + * - PBRNeutralToneMapper * - Debug/validation tone mapping operators * - LinearToneMapper * - DisplayRangeToneMapper @@ -115,11 +119,22 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { math::float3 operator()(math::float3 x) const noexcept override; }; +/** + * Khronos PBR Neutral tone mapping operator. This tone mapper was designed + * to preserve the appearance of materials across lighting conditions while + * avoiding artifacts in the highlights in high dynamic range conditions. + */ +struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper { + PBRNeutralToneMapper() noexcept; + ~PBRNeutralToneMapper() noexcept final; + + math::float3 operator()(math::float3 x) const noexcept override; +}; + /** * AgX tone mapping operator. */ struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper { - enum class AgxLook : uint8_t { NONE = 0, //!< Base contrast with no look applied PUNCHY, //!< A punchy and more chroma laden look for sRGB displays @@ -183,9 +198,6 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper { /** Returns the contrast of the curve as a strictly positive value. */ float getContrast() const noexcept; - /** Returns how fast scene referred values map to output white as a value between 0.0 and 1.0. */ - float getShoulder() const noexcept; - /** Returns the middle gray point for input values as a value between 0.0 and 1.0. */ float getMidGrayIn() const noexcept; diff --git a/filament/include/filament/TransformManager.h b/filament/include/filament/TransformManager.h index 9afa6897128..5d612a1634c 100644 --- a/filament/include/filament/TransformManager.h +++ b/filament/include/filament/TransformManager.h @@ -26,6 +26,7 @@ #include +#include namespace utils { class Entity; @@ -118,6 +119,29 @@ class UTILS_PUBLIC TransformManager : public FilamentAPI { */ Instance getInstance(utils::Entity e) const noexcept; + /** + * @return the number of Components + */ + size_t getComponentCount() const noexcept; + + /** + * @return true if the this manager has no components + */ + bool empty() const noexcept; + + /** + * Retrieve the `Entity` of the component from its `Instance`. + * @param i Instance of the component obtained from getInstance() + * @return + */ + utils::Entity getEntity(Instance i) const noexcept; + + /** + * Retrieve the Entities of all the components of this manager. + * @return A list, in no particular order, of all the entities managed by this manager. + */ + utils::Entity const* UTILS_NONNULL getEntities() const noexcept; + /** * Enables or disable the accurate translation mode. Disabled by default. * @@ -200,7 +224,7 @@ class UTILS_PUBLIC TransformManager : public FilamentAPI { * @param count The maximum number of children to retrieve. * @return The number of children written to the pointer. */ - size_t getChildren(Instance i, utils::Entity* children, size_t count) const noexcept; + size_t getChildren(Instance i, utils::Entity* UTILS_NONNULL children, size_t count) const noexcept; /** * Returns an iterator to the Instance of the first child of the given parent. @@ -261,7 +285,7 @@ class UTILS_PUBLIC TransformManager : public FilamentAPI { * returns the value set by setTransform(). * @see setTransform() */ - const math::mat4 getTransformAccurate(Instance ci) const noexcept; + math::mat4 getTransformAccurate(Instance ci) const noexcept; /** * Return the world transform of a transform component. @@ -279,7 +303,7 @@ class UTILS_PUBLIC TransformManager : public FilamentAPI { * composition of this component's local transform with its parent's world transform. * @see setTransform() */ - const math::mat4 getWorldTransformAccurate(Instance ci) const noexcept; + math::mat4 getWorldTransformAccurate(Instance ci) const noexcept; /** * Opens a local transform transaction. During a transaction, getWorldTransform() can @@ -308,6 +332,10 @@ class UTILS_PUBLIC TransformManager : public FilamentAPI { * @see openLocalTransformTransaction(), setTransform() */ void commitLocalTransformTransaction() noexcept; + +protected: + // prevent heap allocation + ~TransformManager() = default; }; } // namespace filament diff --git a/filament/include/filament/VertexBuffer.h b/filament/include/filament/VertexBuffer.h index dd844c375a0..fccbd0046f8 100644 --- a/filament/include/filament/VertexBuffer.h +++ b/filament/include/filament/VertexBuffer.h @@ -27,6 +27,9 @@ #include +#include +#include + namespace filament { class FVertexBuffer; @@ -160,14 +163,13 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { * * @param engine Reference to the filament::Engine to associate this VertexBuffer with. * - * @return pointer to the newly created object or nullptr if exceptions are disabled and - * an error occurred. + * @return pointer to the newly created object. * * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of * memory or other resources. * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. */ - VertexBuffer* build(Engine& engine); + VertexBuffer* UTILS_NONNULL build(Engine& engine); private: friend class FVertexBuffer; @@ -206,7 +208,12 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { * and Builder::bufferCount() - 1. * @param bufferObject The handle to the GPU data that will be used in this buffer slot. */ - void setBufferObjectAt(Engine& engine, uint8_t bufferIndex, BufferObject const* bufferObject); + void setBufferObjectAt(Engine& engine, uint8_t bufferIndex, + BufferObject const* UTILS_NONNULL bufferObject); + +protected: + // prevent heap allocation + ~VertexBuffer() = default; }; } // namespace filament diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 4bba745d1f3..3cdd527fac7 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -19,17 +19,19 @@ #ifndef TNT_FILAMENT_VIEW_H #define TNT_FILAMENT_VIEW_H -#include #include #include -#include - #include #include #include +#include + +#include +#include + namespace filament { namespace backend { @@ -66,32 +68,32 @@ class Viewport; */ class UTILS_PUBLIC View : public FilamentAPI { public: - using QualityLevel = QualityLevel; - using BlendMode = BlendMode; - using AntiAliasing = AntiAliasing; - using Dithering = Dithering; - using ShadowType = ShadowType; - - using DynamicResolutionOptions = DynamicResolutionOptions; - using BloomOptions = BloomOptions; - using FogOptions = FogOptions; - using DepthOfFieldOptions = DepthOfFieldOptions; - using VignetteOptions = VignetteOptions; - using RenderQuality = RenderQuality; - using AmbientOcclusionOptions = AmbientOcclusionOptions; - using TemporalAntiAliasingOptions = TemporalAntiAliasingOptions; - using MultiSampleAntiAliasingOptions = MultiSampleAntiAliasingOptions; - using VsmShadowOptions = VsmShadowOptions; - using SoftShadowOptions = SoftShadowOptions; - using ScreenSpaceReflectionsOptions = ScreenSpaceReflectionsOptions; - using GuardBandOptions = GuardBandOptions; - using StereoscopicOptions = StereoscopicOptions; + using QualityLevel = filament::QualityLevel; + using BlendMode = filament::BlendMode; + using AntiAliasing = filament::AntiAliasing; + using Dithering = filament::Dithering; + using ShadowType = filament::ShadowType; + + using DynamicResolutionOptions = filament::DynamicResolutionOptions; + using BloomOptions = filament::BloomOptions; + using FogOptions = filament::FogOptions; + using DepthOfFieldOptions = filament::DepthOfFieldOptions; + using VignetteOptions = filament::VignetteOptions; + using RenderQuality = filament::RenderQuality; + using AmbientOcclusionOptions = filament::AmbientOcclusionOptions; + using TemporalAntiAliasingOptions = filament::TemporalAntiAliasingOptions; + using MultiSampleAntiAliasingOptions = filament::MultiSampleAntiAliasingOptions; + using VsmShadowOptions = filament::VsmShadowOptions; + using SoftShadowOptions = filament::SoftShadowOptions; + using ScreenSpaceReflectionsOptions = filament::ScreenSpaceReflectionsOptions; + using GuardBandOptions = filament::GuardBandOptions; + using StereoscopicOptions = filament::StereoscopicOptions; /** * Sets the View's name. Only useful for debugging. * @param name Pointer to the View's name. The string is copied. */ - void setName(const char* name) noexcept; + void setName(const char* UTILS_NONNULL name) noexcept; /** * Returns the View's name @@ -100,7 +102,7 @@ class UTILS_PUBLIC View : public FilamentAPI { * * @attention Do *not* free the pointer or modify its content. */ - const char* getName() const noexcept; + const char* UTILS_NULLABLE getName() const noexcept; /** * Set this View instance's Scene. @@ -116,19 +118,19 @@ class UTILS_PUBLIC View : public FilamentAPI { * There is no reference-counting. * Make sure to dissociate a Scene from all Views before destroying it. */ - void setScene(Scene* scene); + void setScene(Scene* UTILS_NULLABLE scene); /** * Returns the Scene currently associated with this View. * @return A pointer to the Scene associated to this View. nullptr if no Scene is set. */ - Scene* getScene() noexcept; + Scene* UTILS_NULLABLE getScene() noexcept; /** * Returns the Scene currently associated with this View. * @return A pointer to the Scene associated to this View. nullptr if no Scene is set. */ - Scene const* getScene() const noexcept { + Scene const* UTILS_NULLABLE getScene() const noexcept { return const_cast(this)->getScene(); } @@ -143,7 +145,7 @@ class UTILS_PUBLIC View : public FilamentAPI { * * @param renderTarget Render target associated with view, or nullptr for the swap chain. */ - void setRenderTarget(RenderTarget* renderTarget) noexcept; + void setRenderTarget(RenderTarget* UTILS_NULLABLE renderTarget) noexcept; /** * Gets the offscreen render target associated with this view. @@ -152,7 +154,7 @@ class UTILS_PUBLIC View : public FilamentAPI { * * @see setRenderTarget */ - RenderTarget* getRenderTarget() const noexcept; + RenderTarget* UTILS_NULLABLE getRenderTarget() const noexcept; /** * Sets the rectangular region to render to. @@ -185,7 +187,7 @@ class UTILS_PUBLIC View : public FilamentAPI { * There is no reference-counting. * Make sure to dissociate a Camera from all Views before destroying it. */ - void setCamera(Camera* camera) noexcept; + void setCamera(Camera* UTILS_NONNULL camera) noexcept; /** * Returns the Camera currently associated with this View. @@ -402,13 +404,13 @@ class UTILS_PUBLIC View : public FilamentAPI { * There is no reference-counting. * Make sure to dissociate a ColorGrading from all Views before destroying it. */ - void setColorGrading(ColorGrading* colorGrading) noexcept; + void setColorGrading(ColorGrading* UTILS_NULLABLE colorGrading) noexcept; /** * Returns the color grading transforms currently associated to this view. * @return A pointer to the ColorGrading associated to this View. */ - const ColorGrading* getColorGrading() const noexcept; + const ColorGrading* UTILS_NULLABLE getColorGrading() const noexcept; /** * Sets ambient occlusion options. @@ -691,11 +693,12 @@ class UTILS_PUBLIC View : public FilamentAPI { * - punctual lights * * Stereo rendering depends on device and platform support. To check if stereo rendering is - * supported, use Engine::isStereoSupported(). + * supported, use Engine::isStereoSupported(). If stereo rendering is not supported, then the + * stereoscopic options have no effect. * * @param options The stereoscopic options to use on this view */ - void setStereoscopicOptions(StereoscopicOptions const& options); + void setStereoscopicOptions(StereoscopicOptions const& options) noexcept; /** * Returns the stereoscopic options associated with this View. @@ -713,10 +716,10 @@ class UTILS_PUBLIC View : public FilamentAPI { bool isFrustumCullingEnabled() const noexcept; //! debugging: sets the Camera used for rendering. It may be different from the culling camera. - void setDebugCamera(Camera* camera) noexcept; + void setDebugCamera(Camera* UTILS_NULLABLE camera) noexcept; //! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing. - Camera const* getDirectionalLightCamera() const noexcept; + Camera const* UTILS_NULLABLE getDirectionalShadowCamera() const noexcept; /** Result of a picking query */ @@ -745,11 +748,12 @@ class UTILS_PUBLIC View : public FilamentAPI { /** User data for PickingQueryResultCallback */ struct PickingQuery { // note: this is enough to store a std::function<> -- just saying... - void* storage[4]; + void* UTILS_NULLABLE storage[4]; }; /** callback type used for picking queries. */ - using PickingQueryResultCallback = void(*)(PickingQueryResult const& result, PickingQuery* pq); + using PickingQueryResultCallback = + void(*)(PickingQueryResult const& result, PickingQuery* UTILS_NONNULL pq); /** * Helper for creating a picking query from Foo::method, by pointer. @@ -763,10 +767,10 @@ class UTILS_PUBLIC View : public FilamentAPI { * @param handler Handler to dispatch the callback or nullptr for the default handler. */ template - void pick(uint32_t x, uint32_t y, T* instance, backend::CallbackHandler* handler = nullptr) noexcept { + void pick(uint32_t x, uint32_t y, T* UTILS_NONNULL instance, + backend::CallbackHandler* UTILS_NULLABLE handler = nullptr) noexcept { PickingQuery& query = pick(x, y, [](PickingQueryResult const& result, PickingQuery* pq) { - void* user = pq->storage; - (*static_cast(user)->*method)(result); + (static_cast(pq->storage[0])->*method)(result); }, handler); query.storage[0] = instance; } @@ -783,11 +787,11 @@ class UTILS_PUBLIC View : public FilamentAPI { * @param handler Handler to dispatch the callback or nullptr for the default handler. */ template - void pick(uint32_t x, uint32_t y, T instance, backend::CallbackHandler* handler = nullptr) noexcept { + void pick(uint32_t x, uint32_t y, T instance, + backend::CallbackHandler* UTILS_NULLABLE handler = nullptr) noexcept { static_assert(sizeof(instance) <= sizeof(PickingQuery::storage), "user data too large"); PickingQuery& query = pick(x, y, [](PickingQueryResult const& result, PickingQuery* pq) { - void* user = pq->storage; - T* that = static_cast(user); + T* const that = static_cast(reinterpret_cast(pq->storage)); (that->*method)(result); that->~T(); }, handler); @@ -804,15 +808,15 @@ class UTILS_PUBLIC View : public FilamentAPI { * @param handler Handler to dispatch the callback or nullptr for the default handler. */ template - void pick(uint32_t x, uint32_t y, T functor, backend::CallbackHandler* handler = nullptr) noexcept { + void pick(uint32_t x, uint32_t y, T functor, + backend::CallbackHandler* UTILS_NULLABLE handler = nullptr) noexcept { static_assert(sizeof(functor) <= sizeof(PickingQuery::storage), "functor too large"); PickingQuery& query = pick(x, y, handler, (PickingQueryResultCallback)[](PickingQueryResult const& result, PickingQuery* pq) { - void* user = pq->storage; - T& that = *static_cast(user); - that(result); - that.~T(); - }); + T* const that = static_cast(reinterpret_cast(pq->storage)); + that->operator()(result); + that->~T(); + }); new(query.storage) T(std::move(functor)); } @@ -831,8 +835,9 @@ class UTILS_PUBLIC View : public FilamentAPI { * 8*sizeof(void*) bytes of user data. This user data is later accessible * in the PickingQueryResultCallback callback 3rd parameter. */ - PickingQuery& pick(uint32_t x, uint32_t y, backend::CallbackHandler* handler, - PickingQueryResultCallback callback) noexcept; + PickingQuery& pick(uint32_t x, uint32_t y, + backend::CallbackHandler* UTILS_NULLABLE handler, + PickingQueryResultCallback UTILS_NONNULL callback) noexcept; /** * Set the value of material global variables. There are up-to four such variable each of @@ -894,6 +899,10 @@ class UTILS_PUBLIC View : public FilamentAPI { */ UTILS_DEPRECATED AmbientOcclusion getAmbientOcclusion() const noexcept; + +protected: + // prevent heap allocation + ~View() = default; }; } // namespace filament diff --git a/filament/include/filament/Viewport.h b/filament/include/filament/Viewport.h index a641e7ab568..29916f704e8 100644 --- a/filament/include/filament/Viewport.h +++ b/filament/include/filament/Viewport.h @@ -23,9 +23,6 @@ #include -#include -#include - #include #include diff --git a/filament/src/Allocators.h b/filament/src/Allocators.h index eb354b8d329..84962e30c0e 100644 --- a/filament/src/Allocators.h +++ b/filament/src/Allocators.h @@ -54,7 +54,7 @@ using LinearAllocatorArena = utils::Arena< #endif -using ArenaScope = utils::ArenaScope; +using RootArenaScope = utils::ArenaScope; } // namespace filament diff --git a/filament/src/Bimap.h b/filament/src/Bimap.h new file mode 100644 index 00000000000..c765b542a0f --- /dev/null +++ b/filament/src/Bimap.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BIMAP_H +#define TNT_FILAMENT_BIMAP_H + +#include + +#include + +#include +#include +#include + +#include + +namespace filament { + +/* + * A semi-generic custom bimap. This bimap stores a key/value pair and can retrieve + * the value from the key and the key from the value. + * It is optimized for large keys and small values. The keys are stored out-of-line and are + * never moved. + */ +template, + typename ValueHash = std::hash, + typename Allocator = std::allocator> +class Bimap { + + struct KeyDelegate { + Key const* pKey = nullptr; + bool operator==(KeyDelegate const& rhs) const noexcept { + return *pKey == *rhs.pKey; + } + }; + + // KeyWrapperHash delegates the hash computation to KeyHash + struct KeyHasherDelegate { + size_t operator()(KeyDelegate const& p) const noexcept { + KeyHash const h; + return h(*p.pKey); + } + }; + + using ForwardMap = tsl::robin_map; + using BackwardMap = tsl::robin_map; + + Allocator mAllocator; + ForwardMap mForwardMap; + BackwardMap mBackwardMap; + +public: + Bimap() = default; + explicit Bimap(Allocator&& allocator) + : mAllocator(std::forward(allocator)) { + } + + void reserve(size_t capacity) { + mForwardMap.reserve(capacity); + mBackwardMap.reserve(capacity); + } + + bool empty() const noexcept { + return mForwardMap.empty() && mBackwardMap.empty(); + } + + // insert a new key/value pair + void insert(Key const& key, Value const& value) noexcept { + Key* pKey = mAllocator.allocate(1); // allocate storage for the key + new((void*)pKey) Key{ key }; // copy-construct the key + mForwardMap.insert({{ pKey }, value }); + mBackwardMap.insert({ value, { pKey }}); + } + + typename ForwardMap::iterator end() { return mForwardMap.end(); } + + // Find the value iterator from the key in O(1) + typename ForwardMap::const_iterator find(Key const& key) const { + return mForwardMap.find(KeyDelegate{ .pKey = &key }); + } + typename ForwardMap::iterator find(Key const& key) { + return mForwardMap.find(KeyDelegate{ .pKey = &key }); + } + + // Find the key iterator from the value in O(1). precondition, the value must exist. + typename BackwardMap::const_iterator find(Value const& value) const { + auto pos = mBackwardMap.find(value); + assert_invariant( pos != mBackwardMap.end() ); + return pos; + } + typename BackwardMap::iterator find(Value& value) const { + return mBackwardMap.find(value); + } + + // erase a key/value pair using an iterator to the value + void erase(typename BackwardMap::const_iterator it) { + // find the key + Key const& key = *(it->second.pKey); + // and its iterator + auto pos = find(key); + // destroy the key + it->second.pKey->~Key(); + // free its memory + mAllocator.deallocate(const_cast(it->second.pKey), 1); + // remove the entries from both maps + mForwardMap.erase(pos); + mBackwardMap.erase(it); + } +}; + +} // namespace filament + + +#endif // TNT_FILAMENT_BIMAP_H diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 01b956484c4..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(); } @@ -327,8 +332,16 @@ size_t Engine::getMaxAutomaticInstances() const noexcept { return downcast(this)->getMaxAutomaticInstances(); } -bool Engine::isStereoSupported() const noexcept { - return downcast(this)->isStereoSupported(); +const Engine::Config& Engine::getConfig() const noexcept { + return downcast(this)->getConfig(); +} + +bool Engine::isStereoSupported(StereoscopicType stereoscopicType) const noexcept { + return downcast(this)->isStereoSupported(stereoscopicType); +} + +size_t Engine::getMaxStereoscopicEyes() noexcept { + return FEngine::getMaxStereoscopicEyes(); } #if defined(__EMSCRIPTEN__) diff --git a/filament/src/FrameInfo.cpp b/filament/src/FrameInfo.cpp index 12923e69d37..060eb97ba29 100644 --- a/filament/src/FrameInfo.cpp +++ b/filament/src/FrameInfo.cpp @@ -54,10 +54,19 @@ void FrameInfoManager::terminate(DriverApi& driver) noexcept { void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32_t) noexcept { driver.beginTimerQuery(mQueries[mIndex]); uint64_t elapsed = 0; - if (driver.getTimerQueryValue(mQueries[mLast], &elapsed)) { - mLast = (mLast + 1) % POOL_COUNT; - // conversion to our duration happens here - mFrameTime = std::chrono::duration(elapsed); + TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast], &elapsed); + switch (result) { + case TimerQueryResult::NOT_READY: + // nothing to do + break; + case TimerQueryResult::ERROR: + mLast = (mLast + 1) % POOL_COUNT; + break; + case TimerQueryResult::AVAILABLE: + mLast = (mLast + 1) % POOL_COUNT; + // conversion to our duration happens here + mFrameTime = std::chrono::duration(elapsed); + break; } update(config, mFrameTime); } diff --git a/filament/src/Froxelizer.cpp b/filament/src/Froxelizer.cpp index 52e0d71a845..47bd0d343dd 100644 --- a/filament/src/Froxelizer.cpp +++ b/filament/src/Froxelizer.cpp @@ -104,12 +104,12 @@ Froxelizer::Froxelizer(FEngine& engine) static_assert(std::is_same_v, "Record Buffer must use bytes"); - if (UTILS_UNLIKELY(engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) { + DriverApi& driverApi = engine.getDriverApi(); + + if (UTILS_UNLIKELY(driverApi.getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) { return; } - DriverApi& driverApi = engine.getDriverApi(); - mFroxelBufferEntryCount = std::min( FROXEL_BUFFER_MAX_ENTRY_COUNT, engine.getDriverApi().getMaxUniformBufferSize() / 16u); @@ -168,7 +168,8 @@ void Froxelizer::setProjection(const mat4f& projection, } bool Froxelizer::prepare( - FEngine::DriverApi& driverApi, ArenaScope& arena, filament::Viewport const& viewport, + FEngine::DriverApi& driverApi, RootArenaScope& rootArenaScope, + filament::Viewport const& viewport, const mat4f& projection, float projectionNear, float projectionFar) noexcept { setViewport(viewport); setProjection(projection, projectionNear, projectionFar); @@ -199,12 +200,12 @@ bool Froxelizer::prepare( // light records per froxel (~256 KiB) mLightRecords = { - arena.allocate(getFroxelBufferEntryCount(), CACHELINE_SIZE), + rootArenaScope.allocate(getFroxelBufferEntryCount(), CACHELINE_SIZE), getFroxelBufferEntryCount() }; // froxel thread data (~256 KiB) mFroxelShardedData = { - arena.allocate(GROUP_COUNT, CACHELINE_SIZE), + rootArenaScope.allocate(GROUP_COUNT, CACHELINE_SIZE), uint32_t(GROUP_COUNT) }; diff --git a/filament/src/Froxelizer.h b/filament/src/Froxelizer.h index 27885e24bc7..27ba3c57641 100644 --- a/filament/src/Froxelizer.h +++ b/filament/src/Froxelizer.h @@ -110,7 +110,7 @@ class Froxelizer { * * return true if updateUniforms() needs to be called */ - bool prepare(backend::DriverApi& driverApi, ArenaScope& arena, Viewport const& viewport, + bool prepare(backend::DriverApi& driverApi, RootArenaScope& rootArenaScope, Viewport const& viewport, const math::mat4f& projection, float projectionNear, float projectionFar) noexcept; Froxel getFroxelAt(size_t x, size_t y, size_t z) const noexcept; diff --git a/filament/src/HwRenderPrimitiveFactory.cpp b/filament/src/HwRenderPrimitiveFactory.cpp index 2e996e25951..2c76a3da0a2 100644 --- a/filament/src/HwRenderPrimitiveFactory.cpp +++ b/filament/src/HwRenderPrimitiveFactory.cpp @@ -16,6 +16,16 @@ #include "HwRenderPrimitiveFactory.h" +#include +#include +#include + +#include + +#include +#include +#include + #include namespace filament { @@ -23,96 +33,59 @@ namespace filament { using namespace utils; using namespace backend; -bool operator<(HwRenderPrimitiveFactory::Key const& lhs, - HwRenderPrimitiveFactory::Key const& rhs) noexcept { - if (lhs.vbh == rhs.vbh) { - if (lhs.ibh == rhs.ibh) { - if (lhs.offset == rhs.offset) { - if (lhs.count == rhs.count) { - return lhs.type < rhs.type; - } else { - return lhs.count < rhs.count; - } - } else { - return lhs.offset < rhs.offset; - } - } else { - return lhs.ibh < rhs.ibh; - } - } else { - return lhs.vbh < rhs.vbh; - } -} - -inline bool operator<(HwRenderPrimitiveFactory::Entry const& lhs, - HwRenderPrimitiveFactory::Entry const& rhs) noexcept { - return lhs.key < rhs.key; +size_t HwRenderPrimitiveFactory::Parameters::hash() const noexcept { + return utils::hash::combine(vbh.getId(), + utils::hash::combine(ibh.getId(), + (size_t)type)); } -inline bool operator<(HwRenderPrimitiveFactory::Key const& lhs, - HwRenderPrimitiveFactory::Entry const& rhs) noexcept { - return lhs < rhs.key; -} - -inline bool operator<(HwRenderPrimitiveFactory::Entry const& lhs, - HwRenderPrimitiveFactory::Key const& rhs) noexcept { - return lhs.key < rhs; +bool operator==(HwRenderPrimitiveFactory::Parameters const& lhs, + HwRenderPrimitiveFactory::Parameters const& rhs) noexcept { + return lhs.vbh == rhs.vbh && + lhs.ibh == rhs.ibh && + lhs.type == rhs.type; } // ------------------------------------------------------------------------------------------------ HwRenderPrimitiveFactory::HwRenderPrimitiveFactory() : mArena("HwRenderPrimitiveFactory::mArena", SET_ARENA_SIZE), - mSet(mArena) { - mMap.reserve(256); + mBimap(mArena) { + mBimap.reserve(256); } HwRenderPrimitiveFactory::~HwRenderPrimitiveFactory() noexcept = default; -void HwRenderPrimitiveFactory::terminate(DriverApi& driver) noexcept { - assert_invariant(mMap.empty()); - assert_invariant(mSet.empty()); +void HwRenderPrimitiveFactory::terminate(DriverApi&) noexcept { + assert_invariant(mBimap.empty()); } -RenderPrimitiveHandle HwRenderPrimitiveFactory::create(DriverApi& driver, - VertexBufferHandle vbh, IndexBufferHandle ibh, - PrimitiveType type, uint32_t offset, uint32_t minIndex, uint32_t maxIndex, - uint32_t count) noexcept { - - const Key key = { vbh, ibh, offset, count, type }; +auto HwRenderPrimitiveFactory::create(DriverApi& driver, + backend::VertexBufferHandle vbh, + backend::IndexBufferHandle ibh, + backend::PrimitiveType type) noexcept -> Handle { // see if we already have seen this RenderPrimitive - auto pos = mSet.find(key); + 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) - if (UTILS_LIKELY(pos == mSet.end())) { - // create the backend object - auto handle = driver.createRenderPrimitive(vbh, ibh, - type, offset, minIndex, maxIndex, count); - // insert key/handle in our set with a refcount of 1 - // IMPORTANT: std::set<> doesn't invalidate iterators in insert/erase - auto [ipos, _] = mSet.insert({ key, handle, 1 }); - // map the handle back to the key/payload - mMap.insert({ handle.getId(), ipos }); + if (UTILS_LIKELY(pos == mBimap.end())) { + auto handle = driver.createRenderPrimitive(vbh, ibh, type); + mBimap.insert(key, { handle }); return handle; } - pos->refs++; - return pos->handle; + + ++(pos->first.pKey->refs); + return pos->second.handle; } -void HwRenderPrimitiveFactory::destroy(DriverApi& driver, RenderPrimitiveHandle rph) noexcept { +void HwRenderPrimitiveFactory::destroy(DriverApi& driver, Handle handle) noexcept { // look for this handle in our map - auto pos = mMap.find(rph.getId()); - - // it must be there - assert_invariant(pos != mMap.end()); - - // check the refcount and destroy if needed - auto ipos = pos->second; - if (--ipos->refs == 0) { - mSet.erase(ipos); - mMap.erase(pos); - driver.destroyRenderPrimitive(rph); + auto pos = mBimap.find(Value{ handle }); + if (--pos->second.pKey->refs == 0) { + mBimap.erase(pos); + driver.destroyRenderPrimitive(handle); } } diff --git a/filament/src/HwRenderPrimitiveFactory.h b/filament/src/HwRenderPrimitiveFactory.h index 30583ed1715..6601a0c25ba 100644 --- a/filament/src/HwRenderPrimitiveFactory.h +++ b/filament/src/HwRenderPrimitiveFactory.h @@ -17,17 +17,17 @@ #ifndef TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H #define TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H +#include "Bimap.h" + +#include #include #include -#include - #include -#include - -#include +#include +#include #include namespace filament { @@ -36,6 +36,7 @@ class FEngine; class HwRenderPrimitiveFactory { public: + using Handle = backend::RenderPrimitiveHandle; HwRenderPrimitiveFactory(); ~HwRenderPrimitiveFactory() noexcept; @@ -47,70 +48,74 @@ class HwRenderPrimitiveFactory { void terminate(backend::DriverApi& driver) noexcept; - backend::RenderPrimitiveHandle create(backend::DriverApi& driver, + struct Parameters { // 20 bytes + 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, - backend::PrimitiveType type, - uint32_t offset, - uint32_t minIndex, - uint32_t maxIndex, - uint32_t count) noexcept; + backend::PrimitiveType type) noexcept; - void destroy(backend::DriverApi& driver, - backend::RenderPrimitiveHandle rph) noexcept; + void destroy(backend::DriverApi& driver, Handle handle) noexcept; private: - struct Key { // 20 bytes - backend::VertexBufferHandle vbh; // 4 - backend::IndexBufferHandle ibh; // 4 - uint32_t offset; // 4 - uint32_t count; // 4 - backend::PrimitiveType type; // 4 + 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 Entry { // 28 bytes - Key key; // 20 - backend::RenderPrimitiveHandle handle; // 4 - mutable uint32_t refs; // 4 + struct KeyHasher { + size_t operator()(Key const& p) const noexcept { + return p.params.hash(); + } }; + struct Value { // 4 bytes + Handle handle; + }; + + struct ValueHasher { + size_t operator()(Value const v) const noexcept { + return std::hash()(v.handle.getId()); + } + }; + + friend bool operator==(Value const lhs, Value const rhs) noexcept { + return lhs.handle == rhs.handle; + } // Size of the arena used for the "set" part of the bimap static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024; // Arena for the set<>, using a pool allocator inside a heap area. - using Arena = utils::Arena< - utils::PoolAllocator<64>, // this seems to work with clang and msvc + using PoolAllocatorArena = utils::Arena< + utils::PoolAllocatorWithFallback, utils::LockingPolicy::NoLock, -#ifndef NDEBUG - utils::TrackingPolicy::HighWatermark, -#else utils::TrackingPolicy::Untracked, -#endif utils::AreaPolicy::HeapArea>; - using Set = std::set< - Entry, - std::less<>, - utils::STLAllocator>; - - using Map = tsl::robin_map< - backend::RenderPrimitiveHandle::HandleId, - Set::const_iterator>; // Arena where the set memory is allocated - Arena mArena; - - // set of HwRenderPrimitive data - Set mSet; - - // map of RenderPrimitiveHandle to Set Entry - Map mMap; + PoolAllocatorArena mArena; - friend bool operator<(Key const& lhs, Key const& rhs) noexcept; - friend bool operator<(Key const& lhs, Entry const& rhs) noexcept; - friend bool operator<(Entry const& lhs, Key const& rhs) noexcept; - friend bool operator<(Entry const& lhs, Entry const& rhs) noexcept; + // The special Bimap + Bimap> mBimap; }; } // namespace filament diff --git a/filament/src/HwVertexBufferInfoFactory.cpp b/filament/src/HwVertexBufferInfoFactory.cpp new file mode 100644 index 00000000000..44be4301b38 --- /dev/null +++ b/filament/src/HwVertexBufferInfoFactory.cpp @@ -0,0 +1,93 @@ +/* + * 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 "HwVertexBufferInfoFactory.h" + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +namespace filament { + +using namespace utils; +using namespace backend; + +size_t HwVertexBufferInfoFactory::Parameters::hash() const noexcept { + static_assert((sizeof(*this) % sizeof(uint32_t)) == 0); + return utils::hash::murmur3( + reinterpret_cast(this), sizeof(Parameters) / sizeof(uint32_t), 0); +} + +bool operator==(HwVertexBufferInfoFactory::Parameters const& lhs, + HwVertexBufferInfoFactory::Parameters const& rhs) noexcept { + return !memcmp(&lhs, &rhs, sizeof(HwVertexBufferInfoFactory::Parameters)); +} + +// ------------------------------------------------------------------------------------------------ + +HwVertexBufferInfoFactory::HwVertexBufferInfoFactory() + : mArena("HwVertexBufferInfoFactory::mArena", SET_ARENA_SIZE), + mBimap(mArena) { + mBimap.reserve(256); +} + +HwVertexBufferInfoFactory::~HwVertexBufferInfoFactory() noexcept = default; + +void HwVertexBufferInfoFactory::terminate(DriverApi&) noexcept { + assert_invariant(mBimap.empty()); +} + +auto HwVertexBufferInfoFactory::create(DriverApi& driver, + uint8_t bufferCount, + uint8_t attributeCount, + backend::AttributeArray attributes) noexcept -> Handle { + + 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) + if (UTILS_LIKELY(pos == mBimap.end())) { + auto handle = driver.createVertexBufferInfo( + bufferCount, attributeCount, + attributes); + mBimap.insert(key, { handle }); + return handle; + } + + ++(pos->first.pKey->refs); + return pos->second.handle; +} + +void HwVertexBufferInfoFactory::destroy(DriverApi& driver, Handle handle) noexcept { + // look for this handle in our map + auto pos = mBimap.find(Value{ handle }); + if (--(pos->second.pKey->refs) == 0) { + mBimap.erase(pos); + driver.destroyVertexBufferInfo(handle); + } +} + +} // namespace filament diff --git a/filament/src/HwVertexBufferInfoFactory.h b/filament/src/HwVertexBufferInfoFactory.h new file mode 100644 index 00000000000..c467105b1ad --- /dev/null +++ b/filament/src/HwVertexBufferInfoFactory.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_HWVERTEXBUFFERINFOFACTORY_H +#define TNT_FILAMENT_HWVERTEXBUFFERINFOFACTORY_H + +#include "Bimap.h" + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace filament { + +class FEngine; + +class HwVertexBufferInfoFactory { +public: + using Handle = backend::VertexBufferInfoHandle; + + HwVertexBufferInfoFactory(); + ~HwVertexBufferInfoFactory() noexcept; + + HwVertexBufferInfoFactory(HwVertexBufferInfoFactory const& rhs) = delete; + HwVertexBufferInfoFactory(HwVertexBufferInfoFactory&& rhs) noexcept = delete; + HwVertexBufferInfoFactory& operator=(HwVertexBufferInfoFactory const& rhs) = delete; + HwVertexBufferInfoFactory& operator=(HwVertexBufferInfoFactory&& rhs) noexcept = delete; + + void terminate(backend::DriverApi& driver) noexcept; + + struct Parameters { // 136 bytes + uint8_t bufferCount; + uint8_t attributeCount; + uint8_t padding[2] = {}; + backend::AttributeArray attributes; + size_t hash() const noexcept; + }; + + friend bool operator==(Parameters const& lhs, Parameters const& rhs) noexcept; + + Handle create(backend::DriverApi& driver, + uint8_t bufferCount, + uint8_t attributeCount, + backend::AttributeArray attributes) noexcept; + + void destroy(backend::DriverApi& driver, Handle handle) noexcept; + +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 { + return params == rhs.params; + } + }; + + struct KeyHasher { + size_t operator()(Key const& p) const noexcept { + return p.params.hash(); + } + }; + + struct Value { + Handle handle; + }; + + struct ValueHasher { + size_t operator()(Value const v) const noexcept { + return std::hash()(v.handle.getId()); + } + }; + + friend bool operator==(Value const lhs, Value const rhs) noexcept { + return lhs.handle == rhs.handle; + } + + // Size of the arena used for the "set" part of the bimap + static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024; + + // Arena for the set<>, using a pool allocator inside a heap area. + using PoolAllocatorArena = utils::Arena< + utils::PoolAllocatorWithFallback, + utils::LockingPolicy::NoLock, + utils::TrackingPolicy::Untracked, + utils::AreaPolicy::HeapArea>; + + + // Arena where the set memory is allocated + PoolAllocatorArena mArena; + + // The special Bimap + Bimap> mBimap; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_HWVERTEXBUFFERINFOFACTORY_H diff --git a/filament/src/LightManager.cpp b/filament/src/LightManager.cpp index be0252c1230..cc6bd083e55 100644 --- a/filament/src/LightManager.cpp +++ b/filament/src/LightManager.cpp @@ -22,16 +22,24 @@ namespace filament { using namespace math; +bool LightManager::hasComponent(Entity e) const noexcept { + return downcast(this)->hasComponent(e); +} + size_t LightManager::getComponentCount() const noexcept { return downcast(this)->getComponentCount(); } -utils::Entity const* LightManager::getEntities() const noexcept { - return downcast(this)->getEntities(); +bool LightManager::empty() const noexcept { + return downcast(this)->empty(); } -bool LightManager::hasComponent(Entity e) const noexcept { - return downcast(this)->hasComponent(e); +utils::Entity LightManager::getEntity(LightManager::Instance i) const noexcept { + return downcast(this)->getEntity(i); +} + +utils::Entity const* LightManager::getEntities() const noexcept { + return downcast(this)->getEntities(); } LightManager::Instance LightManager::getInstance(Entity e) const noexcept { diff --git a/filament/src/Material.cpp b/filament/src/Material.cpp index 444dec64cf3..e48de9c0b50 100644 --- a/filament/src/Material.cpp +++ b/filament/src/Material.cpp @@ -25,7 +25,7 @@ MaterialInstance* Material::createInstance(const char* name) const noexcept { } const char* Material::getName() const noexcept { - return downcast(this)->getName().c_str(); + return downcast(this)->getName().c_str_safe(); } Shading Material::getShading() const noexcept { @@ -120,6 +120,10 @@ ReflectionMode Material::getReflectionMode() const noexcept { return downcast(this)->getReflectionMode(); } +FeatureLevel Material::getFeatureLevel() const noexcept { + return downcast(this)->getFeatureLevel(); +} + bool Material::hasParameter(const char* name) const noexcept { return downcast(this)->hasParameter(name); } diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index 3b492bd6a4f..5427835a282 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -277,6 +277,16 @@ bool MaterialParser::getBlendingMode(BlendingMode* value) const noexcept { return mImpl.getFromSimpleChunk(ChunkType::MaterialBlendingMode, reinterpret_cast(value)); } +bool MaterialParser::getCustomBlendFunction(std::array* value) const noexcept { + uint32_t blendFunctions = 0; + bool const result = mImpl.getFromSimpleChunk(ChunkType::MaterialBlendFunction, &blendFunctions); + (*value)[0] = BlendFunction((blendFunctions >> 24) & 0xFF); + (*value)[1] = BlendFunction((blendFunctions >> 16) & 0xFF); + (*value)[2] = BlendFunction((blendFunctions >> 8) & 0xFF); + (*value)[3] = BlendFunction((blendFunctions >> 0) & 0xFF); + return result; +} + bool MaterialParser::getMaskThreshold(float* value) const noexcept { return mImpl.getFromSimpleChunk(ChunkType::MaterialMaskThreshold, value); } diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 955c8b2a152..638e93d5662 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -102,6 +102,7 @@ class MaterialParser { bool getShading(Shading*) const noexcept; bool getBlendingMode(BlendingMode*) const noexcept; + bool getCustomBlendFunction(std::array*) const noexcept; bool getMaskThreshold(float*) const noexcept; bool getAlphaToCoverageSet(bool*) const noexcept; bool getAlphaToCoverage(bool*) const noexcept; diff --git a/filament/src/PerShadowMapUniforms.cpp b/filament/src/PerShadowMapUniforms.cpp index 1c1ac24b729..3515ef2b436 100644 --- a/filament/src/PerShadowMapUniforms.cpp +++ b/filament/src/PerShadowMapUniforms.cpp @@ -71,8 +71,7 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction, s.clipControl = engine.getDriverApi().getClipSpaceParams(); } -void PerShadowMapUniforms::prepareLodBias(Transaction const& transaction, - float bias) noexcept { +void PerShadowMapUniforms::prepareLodBias(Transaction const& transaction, float bias) noexcept { auto& s = edit(transaction); s.lodBias = bias; } diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index f45a5576dad..eaf8df8cb46 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -43,13 +43,14 @@ PerViewUniforms::PerViewUniforms(FEngine& engine) noexcept : mSamplers(PerViewSib::SAMPLER_COUNT) { DriverApi& driver = engine.getDriverApi(); - mSamplerGroupHandle = driver.createSamplerGroup(mSamplers.getSize()); + mSamplerGroupHandle = driver.createSamplerGroup( + mSamplers.getSize(), utils::FixedSizeString<32>("Per-view samplers")); mUniformBufferHandle = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC); if (engine.getDFG().isValid()) { - TextureSampler sampler(TextureSampler::MagFilter::LINEAR); + TextureSampler const sampler(TextureSampler::MagFilter::LINEAR); mSamplers.setSampler(PerViewSib::IBL_DFG_LUT, { engine.getDFG().getTexture(), sampler.getSamplerParams() }); } @@ -81,7 +82,8 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn); mat4f const& headFromWorld = camera.view; - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { + Engine::Config const& config = engine.getConfig(); + for (int i = 0; i < config.stereoscopicEyeCount; i++) { mat4f const& eyeFromHead = camera.eyeFromView[i]; // identity for monoscopic rendering mat4f const& clipFromEye = camera.eyeProjection[i]; // clipFromEye * eyeFromHead * headFromWorld @@ -94,9 +96,10 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n s.clipControl = engine.getDriverApi().getClipSpaceParams(); } -void PerViewUniforms::prepareLodBias(float bias) noexcept { +void PerViewUniforms::prepareLodBias(float bias, float2 derivativesScale) noexcept { auto& s = mUniforms.edit(); s.lodBias = bias; + s.derivativesScale = derivativesScale; } void PerViewUniforms::prepareExposure(float ev100) noexcept { @@ -244,6 +247,7 @@ void PerViewUniforms::prepareMaterialGlobals( } void PerViewUniforms::prepareSSR(Handle ssr, + bool disableSSR, float refractionLodOffset, ScreenSpaceReflectionsOptions const& ssrOptions) noexcept { @@ -254,7 +258,7 @@ void PerViewUniforms::prepareSSR(Handle ssr, auto& s = mUniforms.edit(); s.refractionLodOffset = refractionLodOffset; - s.ssrDistance = ssrOptions.enabled ? ssrOptions.maxDistance : 0.0f; + s.ssrDistance = (ssrOptions.enabled && !disableSSR) ? ssrOptions.maxDistance : 0.0f; } void PerViewUniforms::prepareHistorySSR(Handle ssr, diff --git a/filament/src/PerViewUniforms.h b/filament/src/PerViewUniforms.h index 8fb5f529cce..c8df0661903 100644 --- a/filament/src/PerViewUniforms.h +++ b/filament/src/PerViewUniforms.h @@ -69,7 +69,7 @@ class PerViewUniforms { void terminate(backend::DriverApi& driver); void prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept; - void prepareLodBias(float bias) noexcept; + void prepareLodBias(float bias, math::float2 derivativesScale) noexcept; /* * @param viewport viewport (should be same as RenderPassParams::viewport) @@ -95,6 +95,7 @@ class PerViewUniforms { // screen-space reflection and/or refraction (SSR) void prepareSSR(TextureHandle ssr, + bool disableSSR, float refractionLodOffset, ScreenSpaceReflectionsOptions const& ssrOptions) noexcept; diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 4f176c10142..3fbd7f41444 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -27,9 +27,12 @@ #include "details/Engine.h" #include "fg/FrameGraph.h" +#include "fg/FrameGraphId.h" #include "fg/FrameGraphResources.h" +#include "fg/FrameGraphTexture.h" #include "fsr.h" +#include "FrameHistory.h" #include "PerViewUniforms.h" #include "RenderPass.h" @@ -38,16 +41,50 @@ #include "details/Material.h" #include "details/MaterialInstance.h" #include "details/Texture.h" +#include "details/VertexBuffer.h" #include "generated/resources/materials.h" +#include #include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include #include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include + +#include +#include namespace filament { @@ -79,7 +116,7 @@ PostProcessManager::PostProcessMaterial::PostProcessMaterial() noexcept { } PostProcessManager::PostProcessMaterial::PostProcessMaterial(MaterialInfo const& info) noexcept - : PostProcessMaterial() { + : PostProcessMaterial() { mData = info.data; // aliased to mMaterial mSize = info.size; mConstants = info.constants; @@ -131,7 +168,7 @@ void PostProcessManager::PostProcessMaterial::loadMaterial(FEngine& engine) cons mHasMaterial = true; auto builder = Material::Builder(); builder.package(mData, mSize); - for (auto const& constant : mConstants) { + for (auto const& constant: mConstants) { std::visit([&](auto&& arg) { builder.constant(constant.name.data(), constant.name.size(), arg); }, constant.value); @@ -148,69 +185,86 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine) } UTILS_NOINLINE -PipelineState PostProcessManager::PostProcessMaterial::getPipelineState( +std::pair + PostProcessManager::PostProcessMaterial::getPipelineState( FEngine& engine, Variant::type_t variantKey) const noexcept { FMaterial* const material = getMaterial(engine); material->prepareProgram(Variant{ variantKey }); - return { - .program = material->getProgram(Variant{variantKey}), - .rasterState = material->getRasterState(), - .scissor = material->getDefaultInstance()->getScissor() - }; + return {{ + .program = material->getProgram(Variant{ variantKey }), + .vertexBufferInfo = engine.getFullScreenVertexBuffer()->getVertexBufferInfoHandle(), + .rasterState = material->getRasterState() }, + material->getDefaultInstance()->getScissor() }; } UTILS_NOINLINE -FMaterialInstance* PostProcessManager::PostProcessMaterial::getMaterialInstance(FEngine& engine) const noexcept { +FMaterialInstance* PostProcessManager::PostProcessMaterial::getMaterialInstance( + FEngine& engine) const noexcept { FMaterial* const material = getMaterial(engine); return material->getDefaultInstance(); } // ------------------------------------------------------------------------------------------------ -const math::float2 PostProcessManager::sHaltonSamples[16] = { - { filament::halton( 0, 2), filament::halton( 0, 3) }, - { filament::halton( 1, 2), filament::halton( 1, 3) }, - { filament::halton( 2, 2), filament::halton( 2, 3) }, - { filament::halton( 3, 2), filament::halton( 3, 3) }, - { filament::halton( 4, 2), filament::halton( 4, 3) }, - { filament::halton( 5, 2), filament::halton( 5, 3) }, - { filament::halton( 6, 2), filament::halton( 6, 3) }, - { filament::halton( 7, 2), filament::halton( 7, 3) }, - { filament::halton( 8, 2), filament::halton( 8, 3) }, - { filament::halton( 9, 2), filament::halton( 9, 3) }, - { filament::halton(10, 2), filament::halton(10, 3) }, - { filament::halton(11, 2), filament::halton(11, 3) }, - { filament::halton(12, 2), filament::halton(12, 3) }, - { filament::halton(13, 2), filament::halton(13, 3) }, - { filament::halton(14, 2), filament::halton(14, 3) }, - { filament::halton(15, 2), filament::halton(15, 3) } -}; +const PostProcessManager::JitterSequence<4> PostProcessManager::sRGSS4 = {{{ + { 0.625f, 0.125f }, + { 0.125f, 0.375f }, + { 0.875f, 0.625f }, + { 0.375f, 0.875f } +}}}; + +const PostProcessManager::JitterSequence<4> PostProcessManager::sUniformHelix4 = {{{ + { 0.25f, 0.25f }, + { 0.75f, 0.75f }, + { 0.25f, 0.75f }, + { 0.75f, 0.25f }, +}}}; + +template +constexpr auto halton() { + std::array h; + for (size_t i = 0; i < COUNT; i++) { + h[i] = { + filament::halton(i, 2), + filament::halton(i, 3) }; + } + return h; +} + +const PostProcessManager::JitterSequence<32> + PostProcessManager::sHaltonSamples = { halton<32>() }; PostProcessManager::PostProcessManager(FEngine& engine) noexcept : mEngine(engine), - mWorkaroundSplitEasu(false), - mWorkaroundAllowReadOnlyAncillaryFeedbackLoop(false) { + mWorkaroundSplitEasu(false), + mWorkaroundAllowReadOnlyAncillaryFeedbackLoop(false) { } PostProcessManager::~PostProcessManager() noexcept = default; UTILS_NOINLINE -void PostProcessManager::registerPostProcessMaterial(std::string_view name, MaterialInfo const& info) { +void PostProcessManager::registerPostProcessMaterial(std::string_view name, + MaterialInfo const& info) { mMaterialRegistry.try_emplace(name, info); } UTILS_NOINLINE -PostProcessManager::PostProcessMaterial& PostProcessManager::getPostProcessMaterial(std::string_view name) noexcept { +PostProcessManager::PostProcessMaterial& PostProcessManager::getPostProcessMaterial( + std::string_view name) noexcept { assert_invariant(mMaterialRegistry.find(name) != mMaterialRegistry.end()); return mMaterialRegistry[name]; } #define MATERIAL(n) MATERIALS_ ## n ## _DATA, MATERIALS_ ## n ## _SIZE +static const PostProcessManager::MaterialInfo sMaterialListFeatureLevel0[] = { + { "blitLow", MATERIAL(BLITLOW) }, +}; + static const PostProcessManager::MaterialInfo sMaterialList[] = { { "bilateralBlur", MATERIAL(BILATERALBLUR) }, { "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) }, - { "blitLow", MATERIAL(BLITLOW) }, + { "blitArray", MATERIAL(BLITARRAY) }, { "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) }, { "bloomDownsample2x", MATERIAL(BLOOMDOWNSAMPLE2X) }, { "bloomDownsample9", MATERIAL(BLOOMDOWNSAMPLE9) }, @@ -255,6 +309,8 @@ static const PostProcessManager::MaterialInfo sMaterialList[] = { { "fsr_easu_mobileF", MATERIAL(FSR_EASU_MOBILEF) }, { "fsr_rcas", MATERIAL(FSR_RCAS) }, { "debugShadowCascades", MATERIAL(DEBUGSHADOWCASCADES) }, + { "resolveDepth", MATERIAL(RESOLVEDEPTH) }, + { "shadowmap", MATERIAL(SHADOWMAP) }, }; void PostProcessManager::init() noexcept { @@ -273,10 +329,17 @@ void PostProcessManager::init() noexcept { driver.isWorkaroundNeeded(Workaround::ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP); #pragma nounroll - for (auto const& info : sMaterialList) { + for (auto const& info: sMaterialListFeatureLevel0) { registerPostProcessMaterial(info.name, info); } + if (mEngine.getActiveFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1) { + #pragma nounroll + for (auto const& info: sMaterialList) { + registerPostProcessMaterial(info.name, info); + } + } + mStarburstTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::R8, 1, 256, 1, 1, TextureUsage::DEFAULT); @@ -321,7 +384,7 @@ backend::Handle PostProcessManager::getZeroTextureArray() co UTILS_NOINLINE void PostProcessManager::render(FrameGraphResources::RenderPassInfo const& out, - backend::PipelineState const& pipeline, + backend::PipelineState const& pipeline, backend::Viewport const& scissor, DriverApi& driver) const noexcept { assert_invariant( @@ -332,7 +395,8 @@ void PostProcessManager::render(FrameGraphResources::RenderPassInfo const& out, FEngine const& engine = mEngine; Handle const fullScreenQuad = engine.getFullScreenRenderPrimitive(); driver.beginRenderPass(out.target, out.params); - driver.draw(pipeline, fullScreenQuad, 1); + driver.scissor(scissor); + driver.draw(pipeline, fullScreenQuad, 0, 3, 1); driver.endRenderPass(); } @@ -354,7 +418,7 @@ void PostProcessManager::commitAndRender(FrameGraphResources::RenderPassInfo con // ------------------------------------------------------------------------------------------------ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph& fg, - RenderPass const& pass, uint8_t structureRenderFlags, + RenderPassBuilder const& passBuilder, uint8_t structureRenderFlags, uint32_t width, uint32_t height, StructurePassConfig const& config) noexcept { @@ -380,17 +444,14 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph // generate depth pass at the requested resolution auto& structurePass = fg.addPass("Structure Pass", [&](FrameGraph::Builder& builder, auto& data) { - bool const isES2 = mEngine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; + bool const isES2 = mEngine.getDriverApi().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; data.depth = builder.createTexture("Structure Buffer", { .width = width, .height = height, .levels = uint8_t(levelCount), .format = isES2 ? TextureFormat::DEPTH24 : TextureFormat::DEPTH32F }); - // workaround: since we have levels, this implies SAMPLEABLE (because of the gl - // backend, which implements non-sampleables with renderbuffers, which don't have levels). - // (should the gl driver revert to textures, in that case?) data.depth = builder.write(data.depth, - FrameGraphTexture::Usage::DEPTH_ATTACHMENT | FrameGraphTexture::Usage::SAMPLEABLE); + FrameGraphTexture::Usage::DEPTH_ATTACHMENT); if (config.picking) { data.picking = builder.createTexture("Picking Buffer", { @@ -406,17 +467,19 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph .clearFlags = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH }); }, - [=, renderPass = pass](FrameGraphResources const& resources, + [=, passBuilder = passBuilder](FrameGraphResources const& resources, auto const&, DriverApi&) mutable { Variant structureVariant(Variant::DEPTH_VARIANT); structureVariant.setPicking(config.picking); auto out = resources.getRenderPassInfo(); - renderPass.setRenderFlags(structureRenderFlags); - renderPass.setVariant(structureVariant); - renderPass.appendCommands(mEngine, RenderPass::CommandTypeFlags::SSAO); - renderPass.sortCommands(mEngine); - renderPass.execute(mEngine, resources.getPassName(), out.target, out.params); + + passBuilder.renderFlags(structureRenderFlags); + passBuilder.variant(structureVariant); + passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SSAO); + + RenderPass const pass{ passBuilder.build(mEngine) }; + RenderPass::execute(pass, mEngine, resources.getPassName(), out.target, out.params); }); auto depth = structurePass->depth; @@ -463,7 +526,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph // ------------------------------------------------------------------------------------------------ FrameGraphId PostProcessManager::ssr(FrameGraph& fg, - RenderPass const& pass, + RenderPassBuilder const& passBuilder, FrameHistory const& frameHistory, CameraInfo const& cameraInfo, PerViewUniforms& uniforms, @@ -526,7 +589,7 @@ FrameGraphId PostProcessManager::ssr(FrameGraph& fg, }, [this, projection = cameraInfo.projection, userViewMatrix = cameraInfo.getUserViewMatrix(), uvFromClipMatrix, historyProjection, - options, &uniforms, renderPass = pass] + options, &uniforms, passBuilder = passBuilder] (FrameGraphResources const& resources, auto const& data, DriverApi& driver) mutable { // set structure sampler uniforms.prepareStructure(data.structure ? @@ -547,17 +610,17 @@ FrameGraphId PostProcessManager::ssr(FrameGraph& fg, auto out = resources.getRenderPassInfo(); // Remove the HAS_SHADOWING RenderFlags, since it's irrelevant when rendering reflections - RenderPass::RenderFlags flags = renderPass.getRenderFlags(); - flags &= ~RenderPass::HAS_SHADOWING; - renderPass.setRenderFlags(flags); + passBuilder.renderFlags(~RenderPass::HAS_SHADOWING, 0); // use our special SSR variant, it can only be applied to object that have // the SCREEN_SPACE ReflectionMode. - renderPass.setVariant(Variant{Variant::SPECIAL_SSR}); + passBuilder.variant(Variant{ Variant::SPECIAL_SSR }); + // generate all our drawing commands, except blended objects. - renderPass.appendCommands(mEngine, RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS); - renderPass.sortCommands(mEngine); - renderPass.execute(mEngine, resources.getPassName(), out.target, out.params); + passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS); + + RenderPass const pass{ passBuilder.build(mEngine) }; + RenderPass::execute(pass, mEngine, resources.getPassName(), out.target, out.params); }); return ssrPass->reflections; @@ -666,32 +729,34 @@ FrameGraphId PostProcessManager::screenSpaceAmbientOcclusion( FrameGraphId output; }; + // Needed for Vulkan and GLES. Some GLES implementations don't need it. Never needed for Metal. auto& duplicateDepthPass = fg.addPass("Duplicate Depth Pass", [&](FrameGraph::Builder& builder, auto& data) { - // read the depth as an attachment data.input = builder.read(depth, - FrameGraphTexture::Usage::DEPTH_ATTACHMENT); + FrameGraphTexture::Usage::BLIT_SRC); + auto desc = builder.getDescriptor(data.input); desc.levels = 1; // only copy the base level + // create a new buffer for the copy data.output = builder.createTexture("Depth Texture Copy", desc); + + // output is an attachment data.output = builder.write(data.output, - FrameGraphTexture::Usage::DEPTH_ATTACHMENT); - builder.declareRenderPass("Depth Copy RenderTarget", {{ .depth = data.output }}); + FrameGraphTexture::Usage::BLIT_DST); }, - [=](FrameGraphResources const& resources, - auto const& data, DriverApi& driver) { - auto const& desc = resources.getDescriptor(data.input); - auto out = resources.getRenderPassInfo(); - // create a temporary render target for source, needed for the blit. - auto inTarget = driver.createRenderTarget(TargetBufferFlags::DEPTH, - desc.width, desc.height, desc.samples, {}, - { resources.getTexture(data.input) }, {}); - driver.blit(TargetBufferFlags::DEPTH, - out.target, out.params.viewport, - inTarget, out.params.viewport, - SamplerMagFilter::NEAREST); - driver.destroyRenderTarget(inTarget); + [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { + auto const& src = resources.getTexture(data.input); + auto const& dst = resources.getTexture(data.output); + auto const& srcSubDesc = resources.getSubResourceDescriptor(data.input); + auto const& dstSubDesc = resources.getSubResourceDescriptor(data.output); + auto const& desc = resources.getDescriptor(data.output); + assert_invariant(desc.samples == resources.getDescriptor(data.input).samples); + // here we can guarantee that src and dst format and size match, by construction. + driver.blit( + dst, dstSubDesc.level, dstSubDesc.layer, { 0, 0 }, + src, srcSubDesc.level, srcSubDesc.layer, { 0, 0 }, + { desc.width, desc.height }); }); auto& SSAOPass = fg.addPass("SSAO Pass", @@ -742,7 +807,7 @@ FrameGraphId PostProcessManager::screenSpaceAmbientOcclusion( auto ssao = resources.getRenderPassInfo(); auto const& desc = resources.getDescriptor(data.depth); - // estimate of the size in pixel of a 1m tall/wide object viewed from 1m away (i.e. at z=-1) + // Estimate of the size in pixel units of a 1m tall/wide object viewed from 1m away (i.e. at z=-1) const float projectionScale = std::min( 0.5f * cameraInfo.projection[0].x * desc.width, 0.5f * cameraInfo.projection[1].y * desc.height); @@ -814,8 +879,8 @@ FrameGraphId PostProcessManager::screenSpaceAmbientOcclusion( mi->commit(driver); mi->use(driver); - PipelineState pipeline(material.getPipelineState(mEngine)); - pipeline.rasterState.depthFunc = RasterState::DepthFunc::L; + auto pipeline = material.getPipelineState(mEngine); + pipeline.first.rasterState.depthFunc = RasterState::DepthFunc::L; assert_invariant(ssao.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH); render(ssao, pipeline, driver); }); @@ -899,7 +964,7 @@ FrameGraphId PostProcessManager::bilateralBlurPass(FrameGraph auto const& desc = resources.getDescriptor(data.blurred); // unnormalized gaussian half-kernel of a given standard deviation - // returns number of samples stored in array (max 16) + // returns number of samples stored in the array (max 16) constexpr size_t kernelArraySize = 16; // limited by bilateralBlur.mat auto gaussianKernel = [kernelArraySize](float* outKernel, size_t gaussianWidth, float stdDev) -> uint32_t { @@ -929,8 +994,8 @@ FrameGraphId PostProcessManager::bilateralBlurPass(FrameGraph mi->commit(driver); mi->use(driver); - PipelineState pipeline(material.getPipelineState(mEngine)); - pipeline.rasterState.depthFunc = RasterState::DepthFunc::L; + auto pipeline = material.getPipelineState(mEngine); + pipeline.first.rasterState.depthFunc = RasterState::DepthFunc::L; render(blurred, pipeline, driver); }); @@ -943,7 +1008,7 @@ FrameGraphId PostProcessManager::generateGaussianMipmap(Frame auto const subResourceDesc = fg.getSubResourceDescriptor(input); - // create one subresource per level to be generated from the input. These will be our + // Create one subresource per level to be generated from the input. These will be our // destinations. struct MipmapPassData { FixedCapacityVector> out; @@ -1104,7 +1169,7 @@ FrameGraphId PostProcessManager::gaussianBlurPass(FrameGraph& size_t const m = computeGaussianCoefficients(kernel, std::min(sizeof(kernel) / sizeof(*kernel), kernelStorageSize)); - std::string_view sourceParameterName = is2dArray ? "sourceArray"sv : "source"sv; + std::string_view const sourceParameterName = is2dArray ? "sourceArray"sv : "source"sv; // horizontal pass mi->setParameter(sourceParameterName, hwIn, { .filterMag = SamplerMagFilter::LINEAR, @@ -1150,7 +1215,7 @@ PostProcessManager::ScreenSpaceRefConfig PostProcessManager::prepareMipmapSSR(Fr // The kernel-size was determined empirically so that we don't get too many artifacts // due to the down-sampling with a box filter (which happens implicitly). - // requires only 6 stored coefficients and 11 tap/pass + // Requires only 6 stored coefficients and 11 tap/pass // e.g.: size of 13 (4 stored coefficients) // +-------+-------+-------*===*-------+-------+-------+ // ... | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | ... @@ -1314,10 +1379,10 @@ FrameGraphId PostProcessManager::generateMipmapSSR( FrameGraphId output, bool needInputDuplication, ScreenSpaceRefConfig const& config) noexcept { - // descriptor of our actual input image (e.g. reflection buffer or refraction framebuffer) + // Descriptor of our actual input image (e.g. reflection buffer or refraction framebuffer) auto const& desc = fg.getDescriptor(input); - // descriptor of the destination. output is a subresource (i.e. a layer of a 2D array) + // Descriptor of the destination. `output` is a subresource (i.e. a layer of a 2D array) auto const& outDesc = fg.getDescriptor(output); /* @@ -1333,14 +1398,18 @@ FrameGraphId PostProcessManager::generateMipmapSSR( if (desc.samples > 1 && outDesc.width == desc.width && outDesc.height == desc.height && desc.format == outDesc.format) { - // resolve directly into the destination - input = ppm.resolveBaseLevelNoCheck(fg, "ssr", input, outDesc); + // Resolve directly into the destination. This guarantees a blit/resolve will be + // performed (i.e.: the source is copied) and we also guarantee that format/scaling + // is the same after the forwardResource call below. + input = ppm.resolve(fg, "ssr", input, outDesc); } else { - // first resolve (if needed) - input = ppm.resolveBaseLevel(fg, "ssr", input); - // then blit into an appropriate texture - // this handles scaling, format conversion and mipmaping - input = ppm.opaqueBlit(fg, input, { 0, 0, desc.width, desc.height }, outDesc); + // First resolve (if needed), may be a no-op. Guarantees that format/size is unchanged + // by construction. + input = ppm.resolve(fg, "ssr", input, { .levels = 1 }); + // Then blit into an appropriate texture, this handles scaling and format conversion. + // The input/output sizes may differ when non-homogenous DSR is enabled. + input = ppm.blit(fg, false, input, { 0, 0, desc.width, desc.height }, outDesc, + SamplerMagFilter::LINEAR, SamplerMinFilter::LINEAR); } } @@ -1362,7 +1431,7 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, FrameGraphId depth, const CameraInfo& cameraInfo, bool translucent, - float bokehAspectRatio, + float2 bokehScale, const DepthOfFieldOptions& dofOptions) noexcept { assert_invariant(depth); @@ -1373,7 +1442,7 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, const TextureFormat format = translucent ? TextureFormat::RGBA16F : TextureFormat::R11F_G11F_B10F; - // rotate the bokeh based on the aperture diameter (i.e. angle of the blades) + // Rotate the bokeh based on the aperture diameter (i.e. angle of the blades) float bokehAngle = f::PI / 6.0f; if (dofOptions.maxApertureDiameter > 0.0f) { bokehAngle += f::PI_2 * saturate(cameraInfo.A / dofOptions.maxApertureDiameter); @@ -1585,7 +1654,7 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, mi->setParameter("coc", inOutCoc, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); mi->use(driver); - const PipelineState pipeline(material.getPipelineState(mEngine, variant)); + auto const pipeline = material.getPipelineState(mEngine, variant); for (size_t level = 0 ; level < mipmapCount - 1u ; level++) { const float w = FTexture::valueForLevel(level, desc.width); @@ -1617,8 +1686,8 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, // we assume the width/height is already multiple of 16 assert_invariant(!(colorDesc.width & 0xF) && !(colorDesc.height & 0xF)); - const uint32_t tileBufferWidth = colorDesc.width / dofResolution; - const uint32_t tileBufferHeight = colorDesc.height / dofResolution; + const uint32_t tileBufferWidth = width; + const uint32_t tileBufferHeight = height; const size_t tileReductionCount = ctz(tileSize / dofResolution); struct PostProcessDofTiling1 { @@ -1752,8 +1821,8 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, mi->setParameter("tiles", tilesCocMinMax, { .filterMin = SamplerMinFilter::NEAREST }); mi->setParameter("cocToTexelScale", float2{ - bokehAspectRatio / (inputDesc.width * dofResolution), - 1.0 / (inputDesc.height * dofResolution) + bokehScale.x / (inputDesc.width * dofResolution), + bokehScale.y / (inputDesc.height * dofResolution) }); mi->setParameter("cocToPixelScale", (1.0f / float(dofResolution))); mi->setParameter("ringCounts", float4{ @@ -1859,14 +1928,6 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, return ppDoFCombine->output; } -PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, - FrameGraphId input, - BloomOptions& inoutBloomOptions, - backend::TextureFormat outFormat, - math::float2 scale) noexcept { - return bloomPass(fg, input, outFormat, inoutBloomOptions, scale); -} - FrameGraphId PostProcessManager::downscalePass(FrameGraph& fg, FrameGraphId input, FrameGraphTexture::Descriptor const& outDesc, @@ -1898,9 +1959,11 @@ FrameGraphId PostProcessManager::downscalePass(FrameGraph& fg return downsamplePass->output; } -PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg, +PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, FrameGraphId input, TextureFormat outFormat, - BloomOptions& inoutBloomOptions, float2 scale) noexcept { + BloomOptions& inoutBloomOptions, + TemporalAntiAliasingOptions const& taaOptions, + float2 scale) noexcept { // Figure out a good size for the bloom buffer. We must use a fixed bloom buffer size so // that the size/strength of the bloom doesn't vary much with the resolution, otherwise @@ -1942,6 +2005,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg bool threshold = inoutBloomOptions.threshold; + // we don't need to do the fireflies reduction if we have TAA (it already does it) + bool fireflies = threshold && !taaOptions.enabled; + while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) { if (inoutBloomOptions.quality == QualityLevel::LOW || inoutBloomOptions.quality == QualityLevel::MEDIUM) { @@ -1950,8 +2016,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg .height = (desc.height = std::max(1u, desc.height / 2)), .format = outFormat }, - threshold, inoutBloomOptions.highlight, threshold); + threshold, inoutBloomOptions.highlight, fireflies); threshold = false; // we do the thresholding only once during down sampling + fireflies = false; // we do the fireflies reduction only once during down sampling } else if (inoutBloomOptions.quality == QualityLevel::HIGH || inoutBloomOptions.quality == QualityLevel::ULTRA) { // In high quality mode, we increase the size of the bloom buffer such that the @@ -1972,7 +2039,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg input = downscalePass(fg, input, { .width = width, .height = height, .format = outFormat }, - threshold, inoutBloomOptions.highlight, threshold); + threshold, inoutBloomOptions.highlight, fireflies); struct BloomPassData { FrameGraphId out; @@ -2034,7 +2101,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg mi13->commit(driver); // PipelineState for both materials should be the same - const PipelineState pipeline(material9.getPipelineState(mEngine)); + auto const pipeline = material9.getPipelineState(mEngine); for (size_t i = 1; i < inoutBloomOptions.levels; i++) { auto hwDstRT = resources.getRenderPassInfo(data.outRT[i]); @@ -2078,9 +2145,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); mi->use(driver); - PipelineState pipeline(material.getPipelineState(mEngine)); - pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; - pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE; + auto pipeline = material.getPipelineState(mEngine); + pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + pipeline.first.rasterState.blendFunctionDstRGB = BlendFunction::ONE; for (size_t j = inoutBloomOptions.levels, i = j - 1; i >= 1; i--, j++) { auto hwDstRT = resources.getRenderPassInfo(data.outRT[i - 1]); @@ -2233,8 +2300,11 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver, const Variant::type_t variant = Variant::type_t(colorGradingConfig.translucent ? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE); + auto const pipeline = material.getPipelineState(mEngine, variant); + driver.nextSubpass(); - driver.draw(material.getPipelineState(mEngine, variant), fullScreenRenderPrimitive, 1); + driver.scissor(pipeline.second); + driver.draw(pipeline.first, fullScreenRenderPrimitive, 0, 3, 1); } void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomResolveOp op) noexcept { @@ -2250,10 +2320,12 @@ void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept { Handle const& fullScreenRenderPrimitive = engine.getFullScreenRenderPrimitive(); auto const& material = getPostProcessMaterial("customResolveAsSubpass"); // the UBO has been set and committed in colorGradingPrepareSubpass() + auto const pipeline = material.getPipelineState(mEngine); FMaterialInstance* mi = material.getMaterialInstance(mEngine); mi->use(driver); driver.nextSubpass(); - driver.draw(material.getPipelineState(mEngine), fullScreenRenderPrimitive, 1); + driver.scissor(pipeline.second); + driver.draw(pipeline.first, fullScreenRenderPrimitive, 0, 3, 1); } FrameGraphId PostProcessManager::customResolveUncompressPass(FrameGraph& fg, @@ -2481,7 +2553,9 @@ FrameGraphId PostProcessManager::fxaa(FrameGraph& fg, return ppFXAA->output; } -void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& svp, +void PostProcessManager::prepareTaa(FrameGraph& fg, + filament::Viewport const& svp, + TemporalAntiAliasingOptions const& taaOptions, FrameHistory& frameHistory, FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa, CameraInfo* inoutCameraInfo, @@ -2493,9 +2567,35 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv current.projection = inoutCameraInfo->projection * inoutCameraInfo->getUserViewMatrix(); current.frameId = previous.frameId + 1; + auto jitterPosition = [pattern = taaOptions.jitterPattern](size_t frameIndex){ + using JitterPattern = TemporalAntiAliasingOptions::JitterPattern; + switch (pattern) { + case JitterPattern::RGSS_X4: + return sRGSS4(frameIndex); + case JitterPattern::UNIFORM_HELIX_X4: + return sUniformHelix4(frameIndex); + case JitterPattern::HALTON_23_X8: + return sHaltonSamples(frameIndex % 8); + case JitterPattern::HALTON_23_X16: + return sHaltonSamples(frameIndex % 16); + case JitterPattern::HALTON_23_X32: + return sHaltonSamples(frameIndex); + } + }; + // sample position within a pixel [-0.5, 0.5] - float2 const jitter = halton(previous.frameId) - 0.5f; - current.jitter = jitter; + // for metal/vulkan we need to reverse the y-offset + current.jitter = jitterPosition(previous.frameId); + float2 jitter = current.jitter; + switch (mEngine.getBackend()) { + case Backend::VULKAN: + case Backend::METAL: + jitter.y = -jitter.y; + UTILS_FALLTHROUGH; + case Backend::OPENGL: + default: + break; + } float2 const jitterInClipSpace = jitter * (2.0f / float2{ svp.width, svp.height }); @@ -2512,6 +2612,38 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv }); } +void PostProcessManager::configureTemporalAntiAliasingMaterial( + TemporalAntiAliasingOptions const& taaOptions) noexcept { + + FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine); + bool dirty = false; + + auto setConstantParameter = + [&dirty](FMaterial* const ma, std::string_view name, auto value) noexcept { + auto id = ma->getSpecializationConstantId(name); + if (id.has_value()) { + if (ma->setConstant(id.value(), value)) { + dirty = true; + } + } + }; + + setConstantParameter(ma, "upscaling", taaOptions.upscaling); + setConstantParameter(ma, "historyReprojection", taaOptions.historyReprojection); + setConstantParameter(ma, "filterHistory", taaOptions.filterHistory); + setConstantParameter(ma, "filterInput", taaOptions.filterInput); + setConstantParameter(ma, "useYCoCg", taaOptions.useYCoCg); + setConstantParameter(ma, "preventFlickering", taaOptions.preventFlickering); + setConstantParameter(ma, "boxType", (int32_t)taaOptions.boxType); + setConstantParameter(ma, "boxClipping", (int32_t)taaOptions.boxClipping); + setConstantParameter(ma, "varianceGamma", taaOptions.varianceGamma); + if (dirty) { + ma->invalidate(); + // TODO: call Material::compile(), we can't si that now because it works only + // with surface materials + } +} + FrameGraphId PostProcessManager::taa(FrameGraph& fg, FrameGraphId input, FrameGraphId depth, @@ -2544,6 +2676,10 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, auto& taaPass = fg.addPass("TAA", [&](FrameGraph::Builder& builder, auto& data) { auto desc = fg.getDescriptor(input); + if (taaOptions.upscaling) { + desc.width *= 2; + desc.height *= 2; + } data.color = builder.sample(input); data.depth = builder.sample(depth); data.history = builder.sample(colorHistory); @@ -2578,18 +2714,25 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, { -1.0f, 1.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f }, }; - float sum = 0.0; - float weights[9]; + constexpr float2 subSampleOffsets[4] = { + { -0.25f, 0.25f }, { 0.25f, 0.25f }, { 0.25f, -0.25f }, { -0.25f, -0.25f } + }; + + float4 sum = 0.0; + float4 weights[9]; // this doesn't get vectorized (probably because of exp()), so don't bother // unrolling it. #pragma nounroll for (size_t i = 0; i < 9; i++) { - float2 d = sampleOffsets[i] - current.jitter; - d *= 1.0f / taaOptions.filterWidth; - // this is a gaussian fit of a 3.3 Blackman Harris window - // see: "High Quality Temporal Supersampling" by Brian Karis - weights[i] = std::exp2(-3.3f * (d.x * d.x + d.y * d.y)); + float2 const o = sampleOffsets[i]; + for (size_t j = 0; j < 4; j++) { + float2 const s = taaOptions.upscaling ? subSampleOffsets[j] : float2{ 0 }; + float2 const d = (o - current.jitter - s) / taaOptions.filterWidth; + // This is a gaussian fit of a 3.3-wide Blackman-Harris window + // see: "High Quality Temporal Supersampling" by Brian Karis + weights[i][j] = std::exp(-2.29f * (d.x * d.x + d.y * d.y)); + } sum += weights[i]; } for (auto& w : weights) { @@ -2600,8 +2743,8 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, auto color = resources.getTexture(data.color); auto depth = resources.getTexture(data.depth); auto history = resources.getTexture(data.history); - auto const& material = getPostProcessMaterial("taa"); + FMaterialInstance* mi = material.getMaterialInstance(mEngine); mi->setParameter("color", color, {}); // nearest mi->setParameter("depth", depth, {}); // nearest @@ -2611,6 +2754,7 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, .filterMin = SamplerMinFilter::LINEAR }); mi->setParameter("filterWeights", weights, 9); + mi->setParameter("jitter", current.jitter); mi->setParameter("reprojection", mat4f{ historyProjection * inverse(current.projection) } * normalizedToClip); @@ -2624,9 +2768,10 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, if (colorGradingConfig.asSubpass) { out.params.subpassMask = 1; } - PipelineState const pipeline(material.getPipelineState(mEngine, variant)); + auto const pipeline = material.getPipelineState(mEngine, variant); driver.beginRenderPass(out.target, out.params); - driver.draw(pipeline, mEngine.getFullScreenRenderPrimitive(), 1); + driver.scissor(pipeline.second); + driver.draw(pipeline.first, mEngine.getFullScreenRenderPrimitive(), 0, 3, 1); if (colorGradingConfig.asSubpass) { colorGradingSubpass(driver, colorGradingConfig); } @@ -2634,69 +2779,77 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, }); input = colorGradingConfig.asSubpass ? taaPass->tonemappedOutput : taaPass->output; + auto history = input; + + // optional sharpen pass from FSR1 + if (taaOptions.sharpness > 0.0f) { + input = rcas(fg, taaOptions.sharpness, + input, fg.getDescriptor(input), colorGradingConfig.translucent); + } struct ExportColorHistoryData { FrameGraphId color; }; - auto& exportHistoryPass = fg.addPass("Export TAA history", + fg.addPass("Export TAA history", [&](FrameGraph::Builder& builder, auto& data) { // We need to use sideEffect here to ensure this pass won't be culled. // The "output" of this pass is going to be used during the next frame as // an "import". builder.sideEffect(); - data.color = builder.sample(input); // FIXME: an access must be declared for detach(), why? - }, [¤t](FrameGraphResources const& resources, auto const& data, - backend::DriverApi&) { - resources.detach(data.color, - ¤t.color, ¤t.desc); + data.color = builder.sample(history); // FIXME: an access must be declared for detach(), why? + }, [¤t](FrameGraphResources const& resources, auto const& data, auto&) { + resources.detach(data.color, ¤t.color, ¤t.desc); }); - return exportHistoryPass->color; + return input; } -FrameGraphId PostProcessManager::opaqueBlit(FrameGraph& fg, - FrameGraphId input, filament::Viewport const& vp, +FrameGraphId PostProcessManager::rcas( + FrameGraph& fg, + float sharpness, + FrameGraphId input, FrameGraphTexture::Descriptor const& outDesc, - SamplerMagFilter filter) noexcept { + bool translucent) { - struct PostProcessScaling { + struct QuadBlitData { FrameGraphId input; FrameGraphId output; }; - auto& ppBlit = fg.addPass("opaque blit", + auto& ppFsrRcas = fg.addPass("FidelityFX FSR1 Rcas", [&](FrameGraph::Builder& builder, auto& data) { + data.input = builder.sample(input); + data.output = builder.createTexture("FFX FSR1 Rcas output", outDesc); + data.output = builder.declareRenderPass(data.output); + }, + [=](FrameGraphResources const& resources, + auto const& data, DriverApi& driver) { - // we currently have no use for this case, so we just assert. This is better for now to trap - // cases that we might not intend. - assert_invariant(fg.getDescriptor(input).samples <= 1); + auto input = resources.getTexture(data.input); + auto out = resources.getRenderPassInfo(); + auto const& outputDesc = resources.getDescriptor(data.input); - data.output = builder.declareRenderPass( - builder.createTexture("opaque blit output", outDesc)); + auto& material = getPostProcessMaterial("fsr_rcas"); + FMaterialInstance* const mi = material.getMaterialInstance(mEngine); - data.input = builder.read(input); + FSRUniforms uniforms; + FSR_SharpeningSetup(&uniforms, { .sharpness = 2.0f - 2.0f * sharpness }); + mi->setParameter("RcasCon", uniforms.RcasCon); + mi->setParameter("color", input, {}); // uses texelFetch + mi->setParameter("resolution", float4{ + outputDesc.width, outputDesc.height, + 1.0f / outputDesc.width, 1.0f / outputDesc.height }); + mi->commit(driver); + mi->use(driver); - // We use a RenderPass for the source here, instead of just creating a render - // target from data.input in the execute closure, because data.input may refer to - // an imported render target and in this case data.input won't resolve to an actual - // HwTexture handle. Using a RenderPass works because data.input will resolve - // to the actual imported render target and will have the correct viewport. - builder.declareRenderPass("opaque blit input", { - .attachments = { .color = { data.input }}, - .viewport = vp - }); - }, - [=](FrameGraphResources const& resources, auto const&, DriverApi& driver) { - auto out = resources.getRenderPassInfo(0); - auto in = resources.getRenderPassInfo(1); - driver.blit(TargetBufferFlags::COLOR, - out.target, out.params.viewport, - in.target, in.params.viewport, - filter); + const uint8_t variant = uint8_t( + translucent ? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE); + + auto const pipeline = material.getPipelineState(mEngine, variant); + render(out, pipeline, driver); }); - // we rely on automatic culling of unused render passes - return ppBlit->output; + return ppFsrRcas->output; } FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool translucent, @@ -2704,10 +2857,6 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, backend::SamplerMagFilter filter) noexcept { - if (UTILS_LIKELY(!translucent && dsrOptions.quality == QualityLevel::LOW)) { - return opaqueBlit(fg, input, vp, outDesc, filter); - } - // The code below cannot handle sub-resources assert_invariant(fg.getSubResourceDescriptor(input).layer == 0); assert_invariant(fg.getSubResourceDescriptor(input).level == 0); @@ -2838,150 +2987,199 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool auto out = resources.getRenderPassInfo(); if (UTILS_UNLIKELY(twoPassesEASU)) { - PipelineState pipeline0(splitEasuMaterial->getPipelineState(mEngine)); - PipelineState pipeline1(easuMaterial->getPipelineState(mEngine)); - pipeline1.rasterState.depthFunc = backend::SamplerCompareFunc::NE; + auto pipeline0 = splitEasuMaterial->getPipelineState(mEngine); + auto pipeline1 = easuMaterial->getPipelineState(mEngine); + pipeline1.first.rasterState.depthFunc = backend::SamplerCompareFunc::NE; if (translucent) { - enableTranslucentBlending(pipeline0); - enableTranslucentBlending(pipeline1); + enableTranslucentBlending(pipeline0.first); + enableTranslucentBlending(pipeline1.first); } driver.beginRenderPass(out.target, out.params); - driver.draw(pipeline0, fullScreenRenderPrimitive, 1); - driver.draw(pipeline1, fullScreenRenderPrimitive, 1); + driver.scissor(pipeline0.second); + driver.draw(pipeline0.first, fullScreenRenderPrimitive, 0, 3, 1); + driver.scissor(pipeline1.second); + driver.draw(pipeline1.first, fullScreenRenderPrimitive, 0, 3, 1); driver.endRenderPass(); } else { - PipelineState pipeline(easuMaterial->getPipelineState(mEngine)); + auto pipeline = easuMaterial->getPipelineState(mEngine); if (translucent) { - enableTranslucentBlending(pipeline); + enableTranslucentBlending(pipeline.first); } - driver.beginRenderPass(out.target, out.params); - driver.draw(pipeline, fullScreenRenderPrimitive, 1); - driver.endRenderPass(); + render(out, pipeline, driver); } }); auto output = ppQuadBlit->output; // if we had to take the low quality fallback, we still do the "sharpen pass" - if (dsrOptions.sharpness > 0.0f && (dsrOptions.quality != QualityLevel::LOW || lowQualityFallback)) { - auto& ppFsrRcas = fg.addPass("FidelityFX FSR1 Rcas", - [&](FrameGraph::Builder& builder, auto& data) { - data.input = builder.sample(output); - data.output = builder.createTexture("FFX FSR1 Rcas output", outDesc); - data.output = builder.declareRenderPass(data.output); - }, - [=](FrameGraphResources const& resources, - auto const& data, DriverApi& driver) { + if (dsrOptions.sharpness > 0.0f && + (dsrOptions.quality != QualityLevel::LOW || lowQualityFallback)) { + output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent); + } - auto color = resources.getTexture(data.input); - auto out = resources.getRenderPassInfo(); - auto const& outputDesc = resources.getDescriptor(data.output); + // we rely on automatic culling of unused render passes + return output; +} - auto& material = getPostProcessMaterial("fsr_rcas"); - FMaterialInstance* const mi = material.getMaterialInstance(mEngine); +FrameGraphId PostProcessManager::blit(FrameGraph& fg, bool translucent, + FrameGraphId input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, + SamplerMagFilter filterMag, + SamplerMinFilter filterMin) noexcept { - FSRUniforms uniforms; - FSR_SharpeningSetup(&uniforms, { .sharpness = 2.0f - 2.0f * dsrOptions.sharpness }); - mi->setParameter("RcasCon", uniforms.RcasCon); - mi->setParameter("color", color, { }); // uses texelFetch - mi->setParameter("resolution", float4{ - outputDesc.width, outputDesc.height, - 1.0f / outputDesc.width, 1.0f / outputDesc.height }); - mi->commit(driver); - mi->use(driver); + // TODO: add support for sub-resources + assert_invariant(fg.getSubResourceDescriptor(input).layer == 0); + assert_invariant(fg.getSubResourceDescriptor(input).level == 0); - const uint8_t variant = uint8_t(translucent ? - PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE); + struct QuadBlitData { + FrameGraphId input; + FrameGraphId output; + }; - PipelineState const pipeline(material.getPipelineState(mEngine, variant)); - render(out, pipeline, driver); + auto& ppQuadBlit = fg.addPass("blitting", + [&](FrameGraph::Builder& builder, auto& data) { + data.input = builder.sample(input); + data.output = builder.createTexture("upscaled output", outDesc); + data.output = builder.write(data.output, + FrameGraphTexture::Usage::COLOR_ATTACHMENT); + builder.declareRenderPass(builder.getName(data.output), { + .attachments = { .color = { data.output }}, + .clearFlags = TargetBufferFlags::DEPTH }); + }, + [=](FrameGraphResources const& resources, + auto const& data, DriverApi& driver) { + auto color = resources.getTexture(data.input); + auto const& inputDesc = resources.getDescriptor(data.input); + auto const& outputDesc = resources.getDescriptor(data.output); + auto out = resources.getRenderPassInfo(); + + // -------------------------------------------------------------------------------- + // set uniforms + + PostProcessMaterial const& material = getPostProcessMaterial("blitLow"); + auto* mi = material.getMaterialInstance(mEngine); + mi->setParameter("color", color, { + .filterMag = filterMag, + .filterMin = filterMin }); + mi->setParameter("resolution", + float4{ outputDesc.width, outputDesc.height, + 1.0f / outputDesc.width, 1.0f / outputDesc.height }); + mi->setParameter("viewport", float4{ + float(vp.left) / inputDesc.width, + float(vp.bottom) / inputDesc.height, + float(vp.width) / inputDesc.width, + float(vp.height) / inputDesc.height + }); + mi->commit(driver); + mi->use(driver); - output = ppFsrRcas->output; - } + auto pipeline = material.getPipelineState(mEngine); + if (translucent) { + pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + pipeline.first.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; + pipeline.first.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA; + pipeline.first.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA; + } + render(out, pipeline, driver); + }); - // we rely on automatic culling of unused render passes - return output; + return ppQuadBlit->output; } -FrameGraphId PostProcessManager::blit(FrameGraph& fg, bool translucent, - FrameGraphId input, filament::Viewport const& vp, - FrameGraphTexture::Descriptor const& outDesc, - backend::SamplerMagFilter filter) noexcept { - // for now, we implement this by calling upscale() with the low-quality setting. - return upscale(fg, translucent, { .quality = QualityLevel::LOW }, input, vp, outDesc, filter); -} +FrameGraphId PostProcessManager::resolve(FrameGraph& fg, + const char* outputBufferName, FrameGraphId input, + FrameGraphTexture::Descriptor outDesc) noexcept { -FrameGraphId PostProcessManager::resolveBaseLevel(FrameGraph& fg, - const char* outputBufferName, FrameGraphId input) noexcept { // Don't do anything if we're not a MSAA buffer - auto desc = fg.getDescriptor(input); - if (desc.samples <= 1) { + auto const& inDesc = fg.getDescriptor(input); + if (inDesc.samples <= 1) { return input; } - desc.samples = 0; - desc.levels = 1; - return resolveBaseLevelNoCheck(fg, outputBufferName, input, desc); -} -FrameGraphId PostProcessManager::resolveBaseLevelNoCheck(FrameGraph& fg, - const char* outputBufferName, FrameGraphId input, - FrameGraphTexture::Descriptor const& desc) noexcept { + // we currently don't support stencil resolve + assert_invariant(!isStencilFormat(inDesc.format)); + + // The Metal / Vulkan backends currently don't support depth/stencil resolve. + if (isDepthFormat(inDesc.format) && (!mEngine.getDriverApi().isDepthStencilResolveSupported())) { + return resolveDepth(fg, outputBufferName, input, outDesc); + } + + outDesc.width = inDesc.width; + outDesc.height = inDesc.height; + outDesc.format = inDesc.format; + outDesc.samples = 0; struct ResolveData { FrameGraphId input; FrameGraphId output; - backend::TargetBufferFlags inFlags; - FrameGraphTexture::Usage usage; }; auto& ppResolve = fg.addPass("resolve", [&](FrameGraph::Builder& builder, auto& data) { - FrameGraphRenderPass::Descriptor rpDesc; - - auto& rpDescAttachment = isDepthFormat(desc.format) ? - rpDesc.attachments.depth : - rpDesc.attachments.color[0]; - - data.usage = isDepthFormat(desc.format) ? - FrameGraphTexture::Usage::DEPTH_ATTACHMENT : - FrameGraphTexture::Usage::COLOR_ATTACHMENT; + data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC); + data.output = builder.createTexture(outputBufferName, outDesc); + data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST); + }, + [](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { + auto const& src = resources.getTexture(data.input); + auto const& dst = resources.getTexture(data.output); + auto const& srcSubDesc = resources.getSubResourceDescriptor(data.input); + auto const& dstSubDesc = resources.getSubResourceDescriptor(data.output); + UTILS_UNUSED_IN_RELEASE auto const& srcDesc = resources.getDescriptor(data.input); + UTILS_UNUSED_IN_RELEASE auto const& dstDesc = resources.getDescriptor(data.output); + assert_invariant(src); + assert_invariant(dst); + assert_invariant(srcDesc.format == dstDesc.format); + assert_invariant(srcDesc.width == dstDesc.width && srcDesc.height == dstDesc.height); + driver.resolve( + dst, dstSubDesc.level, dstSubDesc.layer, + src, srcSubDesc.level, srcSubDesc.layer); + }); - data.inFlags = isDepthFormat(desc.format) ? - backend::TargetBufferFlags::DEPTH : - backend::TargetBufferFlags::COLOR0; + return ppResolve->output; +} - data.input = builder.read(input, data.usage); +FrameGraphId PostProcessManager::resolveDepth(FrameGraph& fg, + const char* outputBufferName, FrameGraphId input, + FrameGraphTexture::Descriptor outDesc) noexcept { - rpDescAttachment = builder.createTexture(outputBufferName, desc); - rpDescAttachment = builder.write(rpDescAttachment, data.usage); - data.output = rpDescAttachment; - builder.declareRenderPass("Resolve Pass", rpDesc); - }, - [](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { - auto inDesc = resources.getDescriptor(data.input); - auto in = resources.getTexture(data.input); - auto out = resources.getRenderPassInfo(); + // Don't do anything if we're not a MSAA buffer + auto const& inDesc = fg.getDescriptor(input); + if (inDesc.samples <= 1) { + return input; + } - assert_invariant(in); + UTILS_UNUSED_IN_RELEASE auto const& inSubDesc = fg.getSubResourceDescriptor(input); + assert_invariant(isDepthFormat(inDesc.format)); + assert_invariant(inSubDesc.layer == 0); + assert_invariant(inSubDesc.level == 0); - Handle inRt; - if (data.usage == FrameGraphTexture::Usage::COLOR_ATTACHMENT) { - inRt = driver.createRenderTarget(data.inFlags, - out.params.viewport.width, out.params.viewport.height, - inDesc.samples, {{ in }}, {}, {}); - } - if (data.usage == FrameGraphTexture::Usage::DEPTH_ATTACHMENT) { - inRt = driver.createRenderTarget(data.inFlags, - out.params.viewport.width, out.params.viewport.height, - inDesc.samples, {}, { in }, {}); - } + outDesc.width = inDesc.width; + outDesc.height = inDesc.height; + outDesc.format = inDesc.format; + outDesc.samples = 0; - driver.blit(data.inFlags, - out.target, out.params.viewport, inRt, out.params.viewport, - SamplerMagFilter::NEAREST); + struct ResolveData { + FrameGraphId input; + FrameGraphId output; + }; - driver.destroyRenderTarget(inRt); + auto& ppResolve = fg.addPass("resolveDepth", + [&](FrameGraph::Builder& builder, auto& data) { + data.input = builder.sample(input); + data.output = builder.createTexture(outputBufferName, outDesc); + data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT); + builder.declareRenderPass(builder.getName(data.output), { + .attachments = { .depth = { data.output }}, + .clearFlags = TargetBufferFlags::DEPTH }); + }, + [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { + auto const& input = resources.getTexture(data.input); + auto const& material = getPostProcessMaterial("resolveDepth"); + auto* mi = material.getMaterialInstance(mEngine); + mi->setParameter("depth", input, {}); // NEAREST + commitAndRender(resources.getRenderPassInfo(), material, driver); }); return ppResolve->output; @@ -3026,9 +3224,9 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg auto& material = getPostProcessMaterial("vsmMipmap"); // When generating shadow map mip levels, we want to preserve the 1 texel border. - // (note clearing never respects the scissor in filament) - PipelineState pipeline(material.getPipelineState(mEngine)); - pipeline.scissor = { 1u, 1u, dim - 2u, dim - 2u }; + // (note clearing never respects the scissor in Filament) + auto const [pipeline, _] = material.getPipelineState(mEngine); + backend::Viewport const scissor = { 1u, 1u, dim - 2u, dim - 2u }; FMaterialInstance* const mi = material.getMaterialInstance(mEngine); mi->setParameter("color", in, { @@ -3039,7 +3237,7 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg mi->setParameter("uvscale", 1.0f / float(dim)); mi->commit(driver); mi->use(driver); - render(out, pipeline, driver); + render(out, pipeline, scissor, driver); if (finalize) { driver.setMinMaxLevels(in, 0, level); @@ -3072,7 +3270,7 @@ FrameGraphId PostProcessManager::debugShadowCascades(FrameGra auto depth = resources.getTexture(data.depth); auto out = resources.getRenderPassInfo(); auto& material = getPostProcessMaterial("debugShadowCascades"); - PipelineState const pipeline(material.getPipelineState(mEngine)); + auto const pipeline = material.getPipelineState(mEngine); FMaterialInstance* mi = material.getMaterialInstance(mEngine); mi->setParameter("color", color, {}); // nearest mi->setParameter("depth", depth, {}); // nearest @@ -3085,4 +3283,129 @@ FrameGraphId PostProcessManager::debugShadowCascades(FrameGra return debugShadowCascadePass->output; } +FrameGraphId PostProcessManager::debugCombineArrayTexture(FrameGraph& fg, + bool translucent, FrameGraphId input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, + SamplerMagFilter filterMag, + SamplerMinFilter filterMin) noexcept { + + auto& inputTextureDesc = fg.getDescriptor(input); + assert_invariant(inputTextureDesc.depth > 1); + assert_invariant(inputTextureDesc.type == SamplerType::SAMPLER_2D_ARRAY); + + // TODO: add support for sub-resources + assert_invariant(fg.getSubResourceDescriptor(input).layer == 0); + assert_invariant(fg.getSubResourceDescriptor(input).level == 0); + + struct QuadBlitData { + FrameGraphId input; + FrameGraphId output; + }; + + auto& ppQuadBlit = fg.addPass("combining array tex", + [&](FrameGraph::Builder& builder, auto& data) { + data.input = builder.sample(input); + data.output = builder.createTexture("upscaled output", outDesc); + data.output = builder.write(data.output, + FrameGraphTexture::Usage::COLOR_ATTACHMENT); + builder.declareRenderPass(builder.getName(data.output), { + .attachments = {.color = { data.output }}, + .clearFlags = TargetBufferFlags::DEPTH }); + }, + [=](FrameGraphResources const& resources, + auto const& data, DriverApi& driver) { + auto color = resources.getTexture(data.input); + auto const& inputDesc = resources.getDescriptor(data.input); + auto out = resources.getRenderPassInfo(); + + // -------------------------------------------------------------------------------- + // set uniforms + + PostProcessMaterial const& material = getPostProcessMaterial("blitArray"); + auto* mi = material.getMaterialInstance(mEngine); + mi->setParameter("color", color, { + .filterMag = filterMag, + .filterMin = filterMin + }); + mi->setParameter("viewport", float4{ + float(vp.left) / inputDesc.width, + float(vp.bottom) / inputDesc.height, + float(vp.width) / inputDesc.width, + float(vp.height) / inputDesc.height + }); + mi->use(driver); + + auto pipeline = material.getPipelineState(mEngine); + if (translucent) { + pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + pipeline.first.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; + pipeline.first.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA; + pipeline.first.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA; + } + + // The width of each view takes up 1/depth of the screen width. + out.params.viewport.width /= inputTextureDesc.depth; + + // Render all layers of the texture to the screen side-by-side. + for (uint32_t i = 0; i < inputTextureDesc.depth; ++i) { + mi->setParameter("layerIndex", i); + mi->commit(driver); + render(out, pipeline, driver); + // From the second draw, don't clear the targetbuffer. + out.params.flags.clear = filament::backend::TargetBufferFlags::NONE; + out.params.flags.discardStart = filament::backend::TargetBufferFlags::NONE; + out.params.viewport.left += out.params.viewport.width; + } + }); + + return ppQuadBlit->output; +} + +FrameGraphId PostProcessManager::debugDisplayShadowTexture( + FrameGraph& fg, + FrameGraphId input, + FrameGraphId shadowmap, float scale, + uint8_t layer, uint8_t level, uint8_t channel, float power) noexcept { + if (shadowmap) { + struct ShadowMapData { + FrameGraphId color; + FrameGraphId depth; + }; + + auto const& desc = fg.getDescriptor(input); + float const ratio = float(desc.height) / float(desc.width); + float const screenScale = float(fg.getDescriptor(shadowmap).height) / float(desc.height); + float2 const s = { screenScale * scale * ratio, screenScale * scale }; + + auto& shadomapDebugPass = fg.addPass("shadowmap debug pass", + [&](FrameGraph::Builder& builder, auto& data) { + data.color = builder.read(input, + FrameGraphTexture::Usage::COLOR_ATTACHMENT); + data.color = builder.write(data.color, + FrameGraphTexture::Usage::COLOR_ATTACHMENT); + data.depth = builder.sample(shadowmap); + builder.declareRenderPass("color target", { + .attachments = { .color = { data.color }} + }); + }, + [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { + auto out = resources.getRenderPassInfo(); + auto in = resources.getTexture(data.depth); + auto const& material = getPostProcessMaterial("shadowmap"); + FMaterialInstance* const mi = material.getMaterialInstance(mEngine); + mi->setParameter("shadowmap", in, { + .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); + mi->setParameter("scale", s); + mi->setParameter("layer", (uint32_t)layer); + mi->setParameter("level", (uint32_t)level); + mi->setParameter("channel", (uint32_t)channel); + mi->setParameter("power", power); + commitAndRender(out, material, driver); + }); + input = shadomapDebugPass->color; + } + return input; +} + + } // namespace filament diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index ca65c70c74e..432a9e3fff3 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -36,6 +36,7 @@ #include +#include #include #include #include @@ -49,6 +50,7 @@ class FMaterialInstance; class FrameGraph; class PerViewUniforms; class RenderPass; +class RenderPassBuilder; struct CameraInfo; class PostProcessManager { @@ -86,6 +88,10 @@ class PostProcessManager { void init() noexcept; void terminate(backend::DriverApi& driver) noexcept; + + void configureTemporalAntiAliasingMaterial( + TemporalAntiAliasingOptions const& taaOptions) noexcept; + // methods below are ordered relative to their position in the pipeline (as much as possible) // structure (depth) pass @@ -94,12 +100,12 @@ class PostProcessManager { FrameGraphId picking; }; StructurePassOutput structure(FrameGraph& fg, - RenderPass const& pass, uint8_t structureRenderFlags, + RenderPassBuilder const& passBuilder, uint8_t structureRenderFlags, uint32_t width, uint32_t height, StructurePassConfig const& config) noexcept; // reflections pass FrameGraphId ssr(FrameGraph& fg, - RenderPass const& pass, + RenderPassBuilder const& passBuilder, FrameHistory const& frameHistory, CameraInfo const& cameraInfo, PerViewUniforms& uniforms, @@ -158,7 +164,7 @@ class PostProcessManager { FrameGraphId depth, const CameraInfo& cameraInfo, bool translucent, - float bokehAspectRatio, + math::float2 bokehScale, const DepthOfFieldOptions& dofOptions) noexcept; // Bloom @@ -167,7 +173,9 @@ class PostProcessManager { FrameGraphId flare; }; BloomPassOutput bloom(FrameGraph& fg, FrameGraphId input, - BloomOptions& inoutBloomOptions, backend::TextureFormat outFormat, + backend::TextureFormat outFormat, + BloomOptions& inoutBloomOptions, + TemporalAntiAliasingOptions const& taaOptions, math::float2 scale) noexcept; FrameGraphId flarePass(FrameGraph& fg, @@ -207,7 +215,9 @@ class PostProcessManager { backend::TextureFormat outFormat, bool translucent) noexcept; // Temporal Anti-aliasing - void prepareTaa(FrameGraph& fg, filament::Viewport const& svp, + void prepareTaa(FrameGraph& fg, + filament::Viewport const& svp, + TemporalAntiAliasingOptions const& taaOptions, FrameHistory& frameHistory, FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa, CameraInfo* inoutCameraInfo, @@ -221,32 +231,43 @@ class PostProcessManager { TemporalAntiAliasingOptions const& taaOptions, ColorGradingConfig const& colorGradingConfig) noexcept; - // Blit/rescaling/resolves - FrameGraphId opaqueBlit(FrameGraph& fg, - FrameGraphId input, filament::Viewport const& vp, - FrameGraphTexture::Descriptor const& outDesc, - backend::SamplerMagFilter filter = backend::SamplerMagFilter::LINEAR) noexcept; + /* + * Blit/rescaling/resolves + */ + // high quality upscaler + // - when translucent, reverts to LINEAR + // - doens't handle sub-resouces FrameGraphId upscale(FrameGraph& fg, bool translucent, DynamicResolutionOptions dsrOptions, FrameGraphId input, filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, - backend::SamplerMagFilter filter = backend::SamplerMagFilter::LINEAR) noexcept; + backend::SamplerMagFilter filter) noexcept; - FrameGraphId blit(FrameGraph& fg, bool translucent, - FrameGraphId input, filament::Viewport const& vp, + FrameGraphId rcas( + FrameGraph& fg, + float sharpness, + FrameGraphId input, FrameGraphTexture::Descriptor const& outDesc, - backend::SamplerMagFilter filter = backend::SamplerMagFilter::LINEAR) noexcept; + bool translucent); - // resolve base level of input and outputs a 1-level texture - FrameGraphId resolveBaseLevel(FrameGraph& fg, - const char* outputBufferName, FrameGraphId input) noexcept; + // upscale/downscale blitter using shaders + FrameGraphId blit(FrameGraph& fg, bool translucent, + FrameGraphId input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, + backend::SamplerMagFilter filterMag, + backend::SamplerMinFilter filterMin) noexcept; + + // Resolves base level of input and outputs a texture from outDesc. + // outDesc with, height, format and samples will be overridden. + FrameGraphId resolve(FrameGraph& fg, + const char* outputBufferName, FrameGraphId input, + FrameGraphTexture::Descriptor outDesc) noexcept; - // resolves base level of input and outputs a texture from outDesc. outDesc must - // have the same dimensions and format as input, or this will fail. - // outDesc can have mipmaps. - FrameGraphId resolveBaseLevelNoCheck(FrameGraph& fg, + // Resolves base level of input and outputs a texture from outDesc. + // outDesc with, height, format and samples will be overridden. + FrameGraphId resolveDepth(FrameGraph& fg, const char* outputBufferName, FrameGraphId input, - FrameGraphTexture::Descriptor const& outDesc) noexcept; + FrameGraphTexture::Descriptor outDesc) noexcept; // VSM shadow mipmap pass FrameGraphId vsmMipmapPass(FrameGraph& fg, @@ -262,53 +283,25 @@ class PostProcessManager { FrameGraphId input, FrameGraphId depth) noexcept; + FrameGraphId debugDisplayShadowTexture(FrameGraph& fg, + FrameGraphId input, + FrameGraphId shadowmap, float scale, + uint8_t layer, uint8_t level, uint8_t channel, float power) noexcept; + + // Combine an array texture pointed to by `input` into a single image, then return it. + // This is only useful to check the multiview rendered scene as a debugging purpose, thus this + // is not expected to be used in normal cases. + FrameGraphId debugCombineArrayTexture(FrameGraph& fg, bool translucent, + FrameGraphId input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, + backend::SamplerMagFilter filterMag, + backend::SamplerMinFilter filterMin) noexcept; + backend::Handle getOneTexture() const; backend::Handle getZeroTexture() const; backend::Handle getOneTextureArray() const; backend::Handle getZeroTextureArray() const; - math::float2 halton(size_t index) const noexcept { - return mHaltonSamples[index & 0xFu]; - } - -private: - FEngine& mEngine; - class PostProcessMaterial; - - struct BilateralPassConfig { - uint8_t kernelSize = 11; - bool bentNormals = false; - float standardDeviation = 1.0f; - float bilateralThreshold = 0.0625f; - float scale = 1.0f; - }; - - FrameGraphId bilateralBlurPass(FrameGraph& fg, - FrameGraphId input, FrameGraphId depth, - math::int2 axis, float zf, backend::TextureFormat format, - BilateralPassConfig const& config) noexcept; - - BloomPassOutput bloomPass(FrameGraph& fg, - FrameGraphId input, backend::TextureFormat outFormat, - BloomOptions& inoutBloomOptions, math::float2 scale) noexcept; - - FrameGraphId downscalePass(FrameGraph& fg, - FrameGraphId input, - FrameGraphTexture::Descriptor const& outDesc, - bool threshold, float highlight, bool fireflies) noexcept; - - void commitAndRender(FrameGraphResources::RenderPassInfo const& out, - PostProcessMaterial const& material, uint8_t variant, - backend::DriverApi& driver) const noexcept; - - void commitAndRender(FrameGraphResources::RenderPassInfo const& out, - PostProcessMaterial const& material, - backend::DriverApi& driver) const noexcept; - - void render(FrameGraphResources::RenderPassInfo const& out, - backend::PipelineState const& pipeline, - backend::DriverApi& driver) const noexcept; - class PostProcessMaterial { public: PostProcessMaterial() noexcept; @@ -327,7 +320,7 @@ class PostProcessManager { FMaterial* getMaterial(FEngine& engine) const noexcept; FMaterialInstance* getMaterialInstance(FEngine& engine) const noexcept; - backend::PipelineState getPipelineState(FEngine& engine, + std::pair getPipelineState(FEngine& engine, Variant::type_t variantKey = 0u) const noexcept; private: @@ -342,21 +335,68 @@ class PostProcessManager { utils::FixedCapacityVector mConstants{}; }; + void registerPostProcessMaterial(std::string_view name, MaterialInfo const& info); + + PostProcessMaterial& getPostProcessMaterial(std::string_view name) noexcept; + + void commitAndRender(FrameGraphResources::RenderPassInfo const& out, + PostProcessMaterial const& material, uint8_t variant, + backend::DriverApi& driver) const noexcept; + + void commitAndRender(FrameGraphResources::RenderPassInfo const& out, + PostProcessMaterial const& material, + backend::DriverApi& driver) const noexcept; + + void render(FrameGraphResources::RenderPassInfo const& out, + backend::PipelineState const& pipeline, backend::Viewport const& scissor, + backend::DriverApi& driver) const noexcept; + + void render(FrameGraphResources::RenderPassInfo const& out, + std::pair const& combo, + backend::DriverApi& driver) const noexcept { + render(out, combo.first, combo.second, driver); + } + +private: + FEngine& mEngine; + + struct BilateralPassConfig { + uint8_t kernelSize = 11; + bool bentNormals = false; + float standardDeviation = 1.0f; + float bilateralThreshold = 0.0625f; + float scale = 1.0f; + }; + + FrameGraphId bilateralBlurPass(FrameGraph& fg, + FrameGraphId input, FrameGraphId depth, + math::int2 axis, float zf, backend::TextureFormat format, + BilateralPassConfig const& config) noexcept; + + FrameGraphId downscalePass(FrameGraph& fg, + FrameGraphId input, + FrameGraphTexture::Descriptor const& outDesc, + bool threshold, float highlight, bool fireflies) noexcept; + using MaterialRegistryMap = tsl::robin_map< std::string_view, PostProcessMaterial>; MaterialRegistryMap mMaterialRegistry; - void registerPostProcessMaterial(std::string_view name, MaterialInfo const& info); - PostProcessMaterial& getPostProcessMaterial(std::string_view name) noexcept; - backend::Handle mStarburstTexture; std::uniform_real_distribution mUniformDistribution{0.0f, 1.0f}; - static const math::float2 sHaltonSamples[16]; - math::float2 const* mHaltonSamples = sHaltonSamples; + template + struct JitterSequence { + auto operator()(size_t i) const noexcept { return positions[i % SIZE] - 0.5f; } + const std::array positions; + }; + + static const JitterSequence<4> sRGSS4; + static const JitterSequence<4> sUniformHelix4; + static const JitterSequence<32> sHaltonSamples; bool mWorkaroundSplitEasu : 1; bool mWorkaroundAllowReadOnlyAncillaryFeedbackLoop : 1; diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 55a66a75d30..16489cb098c 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -19,17 +19,44 @@ #include "RenderPrimitive.h" #include "ShadowMap.h" +#include "details/Camera.h" #include "details/Material.h" #include "details/MaterialInstance.h" #include "details/View.h" +#include "components/RenderableManager.h" + +#include #include +#include + +#include + +#include +#include +#include +#include +#include "private/backend/CircularBuffer.h" + +#include +#include #include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include + using namespace utils; using namespace filament::math; @@ -37,64 +64,112 @@ namespace filament { using namespace backend; -RenderPass::RenderPass(FEngine& engine, - RenderPass::Arena& arena) noexcept - : mCommandArena(arena), - mCustomCommands(engine.getPerRenderPassAllocator()) { +RenderPassBuilder& RenderPassBuilder::customCommand( + FEngine& engine, + uint8_t channel, + RenderPass::Pass pass, + RenderPass::CustomCommand custom, + uint32_t order, + RenderPass::Executor::CustomCommandFn const& command) { + if (!mCustomCommands.has_value()) { + // construct the vector the first time + mCustomCommands.emplace(engine.getPerRenderPassArena()); + } + mCustomCommands->emplace_back(channel, pass, custom, order, command); + return *this; } -RenderPass::RenderPass(RenderPass const& rhs) = default; +RenderPass RenderPassBuilder::build(FEngine& engine) { + ASSERT_POSTCONDITION(mRenderableSoa, "RenderPassBuilder::geometry() hasn't been called"); + assert_invariant(mScissorViewport.width <= std::numeric_limits::max()); + assert_invariant(mScissorViewport.height <= std::numeric_limits::max()); + return RenderPass{ engine, *this }; +} -// this destructor is actually heavy because it inlines ~vector<> -RenderPass::~RenderPass() noexcept = default; +// ------------------------------------------------------------------------------------------------ + +RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexcept + : mRenderableSoa(*builder.mRenderableSoa), + mVisibleRenderables(builder.mVisibleRenderables), + mUboHandle(builder.mUboHandle), + mCameraPosition(builder.mCameraPosition), + mCameraForwardVector(builder.mCameraForwardVector), + mFlags(builder.mFlags), + mVariant(builder.mVariant), + mVisibilityMask(builder.mVisibilityMask), + mScissorViewport(builder.mScissorViewport), + mCustomCommands(engine.getPerRenderPassArena()) { + + // compute the number of commands we need + updateSummedPrimitiveCounts( + const_cast(mRenderableSoa), mVisibleRenderables); + + uint32_t commandCount = + FScene::getPrimitiveCount(mRenderableSoa, mVisibleRenderables.last); + const bool colorPass = bool(builder.mCommandTypeFlags & CommandTypeFlags::COLOR); + const bool depthPass = bool(builder.mCommandTypeFlags & CommandTypeFlags::DEPTH); + commandCount *= uint32_t(colorPass * 2 + depthPass); + commandCount += 1; // for the sentinel + + uint32_t const customCommandCount = + builder.mCustomCommands.has_value() ? builder.mCustomCommands->size() : 0; -RenderPass::Command* RenderPass::append(size_t count) noexcept { - // this is like an "in-place" realloc(). Works only with LinearAllocator. - Command* const curr = mCommandArena.alloc(count); + Command* const curr = builder.mArena.alloc(commandCount + customCommandCount); assert_invariant(curr); - assert_invariant(mCommandBegin == nullptr || curr == mCommandEnd); - if (mCommandBegin == nullptr) { - mCommandBegin = mCommandEnd = curr; + + if (UTILS_UNLIKELY(builder.mArena.getAllocator().isHeapAllocation(curr))) { + static bool sLogOnce = true; + if (UTILS_UNLIKELY(sLogOnce)) { + sLogOnce = false; + PANIC_LOG("RenderPass arena is full, using slower system heap. Please increase " + "the appropriate constant (e.g. FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB)."); + } } - mCommandEnd += count; - return curr; -} -void RenderPass::resize(size_t count) noexcept { - if (mCommandBegin) { - mCommandEnd = mCommandBegin + count; - mCommandArena.rewind(mCommandEnd); + mCommandBegin = curr; + mCommandEnd = curr + commandCount + customCommandCount; + + appendCommands(engine, { curr, commandCount }, builder.mCommandTypeFlags); + + if (builder.mCustomCommands.has_value()) { + Command* p = curr + commandCount; + for (auto [channel, passId, command, order, fn]: builder.mCustomCommands.value()) { + appendCustomCommand(p++, channel, passId, command, order, fn); + } } -} -void RenderPass::setGeometry(FScene::RenderableSoa const& soa, Range vr, - backend::Handle uboHandle) noexcept { - mRenderableSoa = &soa; - mVisibleRenderables = vr; - mUboHandle = uboHandle; -} + // sort commands once we're done adding commands + sortCommands(builder.mArena); -void RenderPass::setCamera(const CameraInfo& camera) noexcept { - mCameraPosition = camera.getPosition(); - mCameraForwardVector = camera.getForwardVector(); + if (engine.isAutomaticInstancingEnabled()) { + instanceify(engine, builder.mArena); + } } -void RenderPass::setScissorViewport(backend::Viewport viewport) noexcept { - assert_invariant(viewport.width <= std::numeric_limits::max()); - assert_invariant(viewport.height <= std::numeric_limits::max()); - mScissorViewport = viewport; +// this destructor is actually heavy because it inlines ~vector<> +RenderPass::~RenderPass() noexcept = default; + +void RenderPass::resize(Arena& arena, size_t count) noexcept { + if (mCommandBegin) { + mCommandEnd = mCommandBegin + count; + arena.rewind(mCommandEnd); + } } -void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandTypeFlags) noexcept { +void RenderPass::appendCommands(FEngine& engine, + Slice commands, CommandTypeFlags const commandTypeFlags) noexcept { SYSTRACE_CALL(); SYSTRACE_CONTEXT(); - assert_invariant(mRenderableSoa); - utils::Range const vr = mVisibleRenderables; // trace the number of visible renderables SYSTRACE_VALUE32("visibleRenderables", vr.size()); if (UTILS_UNLIKELY(vr.empty())) { + // no renderables, we still need the sentinel and the command buffer size should be + // exactly 1. + assert_invariant(commands.size() == 1); + Command* curr = commands.data(); + curr->key = uint64_t(Pass::SENTINEL); return; } @@ -104,26 +179,21 @@ void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandT const FScene::VisibleMaskType visibilityMask = mVisibilityMask; // up-to-date summed primitive counts needed for generateCommands() - FScene::RenderableSoa const& soa = *mRenderableSoa; - updateSummedPrimitiveCounts(const_cast(soa), vr); + FScene::RenderableSoa const& soa = mRenderableSoa; - // compute how much maximum storage we need for this pass - uint32_t commandCount = FScene::getPrimitiveCount(soa, vr.last); - // double the color pass for transparent objects that need to render twice - const bool colorPass = bool(commandTypeFlags & CommandTypeFlags::COLOR); - const bool depthPass = bool(commandTypeFlags & CommandTypeFlags::DEPTH); - commandCount *= uint32_t(colorPass * 2 + depthPass); - commandCount += 1; // for the sentinel - Command* const curr = append(commandCount); + Command* curr = commands.data(); + size_t const commandCount = commands.size(); + + auto stereoscopicEyeCount = engine.getConfig().stereoscopicEyeCount; const float3 cameraPosition(mCameraPosition); const float3 cameraForwardVector(mCameraForwardVector); auto work = [commandTypeFlags, curr, &soa, variant, renderFlags, visibilityMask, cameraPosition, - cameraForwardVector] + cameraForwardVector, stereoscopicEyeCount] (uint32_t startIndex, uint32_t indexCount) { RenderPass::generateCommands(commandTypeFlags, curr, soa, { startIndex, startIndex + indexCount }, variant, renderFlags, visibilityMask, - cameraPosition, cameraForwardVector); + cameraPosition, cameraForwardVector, stereoscopicEyeCount); }; if (vr.size() <= JOBS_PARALLEL_FOR_COMMANDS_COUNT) { @@ -143,13 +213,14 @@ void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandT // This must be done from the main thread. for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) { if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) { - auto ma = first->primitive.mi->getMaterial(); + auto ma = first->primitive.primitive->getMaterialInstance()->getMaterial(); ma->prepareProgram(first->primitive.materialVariant); } } } -void RenderPass::appendCustomCommand(uint8_t channel, Pass pass, CustomCommand custom, uint32_t order, +void RenderPass::appendCustomCommand(Command* commands, + uint8_t channel, Pass pass, CustomCommand custom, uint32_t order, Executor::CustomCommandFn command) { assert_invariant((uint64_t(order) << CUSTOM_ORDER_SHIFT) <= CUSTOM_ORDER_MASK); @@ -165,11 +236,10 @@ void RenderPass::appendCustomCommand(uint8_t channel, Pass pass, CustomCommand c cmd |= uint64_t(order) << CUSTOM_ORDER_SHIFT; cmd |= uint64_t(index); - Command* const curr = append(1); - curr->key = cmd; + commands->key = cmd; } -void RenderPass::sortCommands(FEngine& engine) noexcept { +void RenderPass::sortCommands(Arena& arena) noexcept { SYSTRACE_NAME("sort and trim commands"); std::sort(mCommandBegin, mCommandEnd); @@ -180,30 +250,20 @@ void RenderPass::sortCommands(FEngine& engine) noexcept { return c.key != uint64_t(Pass::SENTINEL); }); - resize(uint32_t(last - mCommandBegin)); - - if (engine.isAutomaticInstancingEnabled()) { - instanceify(engine); - } + resize(arena, uint32_t(last - mCommandBegin)); } -void RenderPass::execute(FEngine& engine, const char* name, +void RenderPass::execute(RenderPass const& pass, + FEngine& engine, const char* name, backend::Handle renderTarget, - backend::RenderPassParams params) const noexcept { - + backend::RenderPassParams params) noexcept { DriverApi& driver = engine.getDriverApi(); - - // this is a good time to flush the CommandStream, because we're about to potentially - // output a lot of commands. This guarantees here that we have at least - // FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB bytes (1MiB by default). - engine.flush(); - driver.beginRenderPass(renderTarget, params); - getExecutor().execute(engine, name); + pass.getExecutor().execute(engine, name); driver.endRenderPass(); } -void RenderPass::instanceify(FEngine& engine) noexcept { +void RenderPass::instanceify(FEngine& engine, Arena& arena) noexcept { SYSTRACE_NAME("instanceify"); // instanceify works by scanning the **sorted** command stream, looking for repeat draw @@ -235,8 +295,7 @@ void RenderPass::instanceify(FEngine& engine) noexcept { [lhs = *curr](Command const& rhs) { // primitives must be identical to be instanced. Currently, instancing doesn't support // skinning/morphing. - return lhs.primitive.mi == rhs.primitive.mi && - lhs.primitive.primitiveHandle == rhs.primitive.primitiveHandle && + return lhs.primitive.primitive == rhs.primitive.primitive && lhs.primitive.rasterState == rhs.primitive.rasterState && lhs.primitive.skinningHandle == rhs.primitive.skinningHandle && lhs.primitive.skinningOffset == rhs.primitive.skinningOffset && @@ -259,7 +318,8 @@ void RenderPass::instanceify(FEngine& engine) noexcept { // buffer large enough for all instances data stagingBufferSize = sizeof(PerRenderableData) * (last - curr); stagingBuffer = (PerRenderableData*)::malloc(stagingBufferSize); - uboData = mRenderableSoa->data(); + uboData = mRenderableSoa.data(); + assert_invariant(uboData); } // copy the ubo data to a staging buffer @@ -312,7 +372,7 @@ void RenderPass::instanceify(FEngine& engine) noexcept { return command.key == uint64_t(Pass::SENTINEL); }); - resize(uint32_t(lastCommand - mCommandBegin)); + resize(arena, uint32_t(lastCommand - mCommandBegin)); } assert_invariant(stagingBuffer == nullptr); @@ -320,7 +380,7 @@ void RenderPass::instanceify(FEngine& engine) noexcept { /* static */ -UTILS_ALWAYS_INLINE // this function exists only to make the code more readable. we want it inlined. +UTILS_ALWAYS_INLINE // This function exists only to make the code more readable. we want it inlined. inline // and we don't need it in the compilation unit void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant, FMaterialInstance const* const UTILS_RESTRICT mi, bool inverseFrontFaces) noexcept { @@ -364,18 +424,17 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant, cmdDraw.primitive.rasterState.colorWrite = mi->isColorWriteEnabled(); cmdDraw.primitive.rasterState.depthWrite = mi->isDepthWriteEnabled(); cmdDraw.primitive.rasterState.depthFunc = mi->getDepthFunc(); - cmdDraw.primitive.mi = mi; cmdDraw.primitive.materialVariant = variant; // we keep "RasterState::colorWrite" to the value set by material (could be disabled) } /* static */ UTILS_NOINLINE -void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const commands, +void RenderPass::generateCommands(CommandTypeFlags commandTypeFlags, Command* const commands, FScene::RenderableSoa const& soa, Range range, Variant variant, RenderFlags renderFlags, - FScene::VisibleMaskType visibilityMask, - float3 cameraPosition, float3 cameraForward) noexcept { + FScene::VisibleMaskType visibilityMask, float3 cameraPosition, float3 cameraForward, + uint8_t stereoEyeCount) noexcept { SYSTRACE_CALL(); @@ -406,11 +465,13 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm switch (commandTypeFlags & (CommandTypeFlags::COLOR | CommandTypeFlags::DEPTH)) { case CommandTypeFlags::COLOR: curr = generateCommandsImpl(commandTypeFlags, curr, - soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward); + soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward, + stereoEyeCount); break; case CommandTypeFlags::DEPTH: curr = generateCommandsImpl(commandTypeFlags, curr, - soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward); + soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward, + stereoEyeCount); break; default: // we should never end-up here @@ -427,13 +488,13 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm } /* static */ -template +template UTILS_NOINLINE -RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, +RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFlags extraFlags, Command* UTILS_RESTRICT curr, FScene::RenderableSoa const& UTILS_RESTRICT soa, Range range, Variant const variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, - float3 cameraPosition, float3 cameraForward) noexcept { + float3 cameraPosition, float3 cameraForward, uint8_t stereoEyeCount) noexcept { // generateCommands() writes both the draw and depth commands simultaneously such that // we go throw the list of renderables just once. @@ -459,7 +520,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, const bool hasShadowing = renderFlags & HAS_SHADOWING; const bool viewInverseFrontFaces = renderFlags & HAS_INVERSE_FRONT_FACES; - const bool hasInstancedStereo = renderFlags & IS_STEREOSCOPIC; + const bool hasInstancedStereo = renderFlags & IS_INSTANCED_STEREOSCOPIC; Command cmdColor; @@ -515,11 +576,12 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, // calculate the per-primitive face winding order inversion const bool inverseFrontFaces = viewInverseFrontFaces ^ soaVisibility[i].reversedWindingOrder; const bool hasMorphing = soaVisibility[i].morphing; - const bool hasSkinningOrMorphing = soaVisibility[i].skinning || hasMorphing; + const bool hasSkinning = soaVisibility[i].skinning; + const bool hasSkinningOrMorphing = hasSkinning || hasMorphing; cmdColor.key = makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT); cmdColor.key |= makeField(soaVisibility[i].channel, CHANNEL_MASK, CHANNEL_SHIFT); - cmdColor.primitive.index = (uint16_t)i; + cmdColor.primitive.index = i; cmdColor.primitive.instanceCount = soaInstanceInfo[i].count | PrimitiveInfo::USER_INSTANCE_MASK; cmdColor.primitive.instanceBufferHandle = soaInstanceInfo[i].handle; @@ -529,7 +591,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, // eye count. if (UTILS_UNLIKELY(hasInstancedStereo)) { cmdColor.primitive.instanceCount = - (soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) | + (soaInstanceInfo[i].count * stereoEyeCount) | PrimitiveInfo::USER_INSTANCE_MASK; } @@ -540,36 +602,46 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, Variant::isSSRVariant(variant) || (soaVisibility[i].receiveShadows & hasShadowing)); renderableVariant.setSkinning(hasSkinningOrMorphing); + const FRenderableManager::SkinningBindingInfo& skinning = soaSkinning[i]; + const FRenderableManager::MorphingBindingInfo& morphing = soaMorphing[i]; + if constexpr (isDepthPass) { cmdDepth.key = uint64_t(Pass::DEPTH); cmdDepth.key |= uint64_t(CustomCommand::PASS); cmdDepth.key |= makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT); cmdDepth.key |= makeField(soaVisibility[i].channel, CHANNEL_MASK, CHANNEL_SHIFT); cmdDepth.key |= makeField(distanceBits >> 22u, Z_BUCKET_MASK, Z_BUCKET_SHIFT); - cmdDepth.primitive.index = (uint16_t)i; + cmdDepth.primitive.index = i; cmdDepth.primitive.instanceCount = soaInstanceInfo[i].count | PrimitiveInfo::USER_INSTANCE_MASK; cmdDepth.primitive.instanceBufferHandle = soaInstanceInfo[i].handle; cmdDepth.primitive.materialVariant.setSkinning(hasSkinningOrMorphing); cmdDepth.primitive.rasterState.inverseFrontFaces = inverseFrontFaces; + cmdDepth.primitive.skinningHandle = skinning.handle; + cmdDepth.primitive.skinningOffset = skinning.offset; + cmdDepth.primitive.skinningTexture = skinning.handleSampler; + cmdDepth.primitive.morphWeightBuffer = morphing.handle; + if (UTILS_UNLIKELY(hasInstancedStereo)) { cmdColor.primitive.instanceCount = - (soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) | + (soaInstanceInfo[i].count * stereoEyeCount) | PrimitiveInfo::USER_INSTANCE_MASK; } } if constexpr (isColorPass) { renderableVariant.setFog(soaVisibility[i].fog && Variant::isFogVariant(variant)); + + cmdColor.primitive.skinningHandle = skinning.handle; + cmdColor.primitive.skinningOffset = skinning.offset; + cmdColor.primitive.skinningTexture = skinning.handleSampler; + cmdColor.primitive.morphWeightBuffer = morphing.handle; } const bool shadowCaster = soaVisibility[i].castShadows & hasShadowing; const bool writeDepthForShadowCasters = depthContainsShadowCasters & shadowCaster; const Slice& primitives = soaPrimitives[i]; - const FRenderableManager::SkinningBindingInfo& skinning = soaSkinning[i]; - const FRenderableManager::MorphingBindingInfo& morphing = soaMorphing[i]; - /* * This is our hot loop. It's written to avoid branches. * When modifying this code, always ensure it stays efficient. @@ -580,15 +652,13 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, FMaterialInstance const* const mi = primitive.getMaterialInstance(); FMaterial const* const ma = mi->getMaterial(); + // TODO: we should disable the SKN variant if this primitive doesn't have either + // skinning or morphing. + if constexpr (isColorPass) { - cmdColor.primitive.primitiveHandle = primitive.getHwHandle(); + cmdColor.primitive.primitive = &primitive; RenderPass::setupColorCommand(cmdColor, renderableVariant, mi, inverseFrontFaces); - cmdColor.primitive.skinningHandle = skinning.handle; - cmdColor.primitive.skinningOffset = skinning.offset; - cmdColor.primitive.skinningTexture = skinning.handleSampler; - - cmdColor.primitive.morphWeightBuffer = morphing.handle; cmdColor.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle(); const bool blendPass = Pass(cmdColor.key & PASS_MASK) == Pass::BLENDED; @@ -683,18 +753,14 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, const bool translucent = (blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED); + // TODO: we should disable the SKN variant if this primitive doesn't have either + // skinning or morphing. + cmdDepth.key |= mi->getSortingKey(); // already all set-up for direct or'ing // unconditionally write the command - cmdDepth.primitive.primitiveHandle = primitive.getHwHandle(); - cmdDepth.primitive.mi = mi; + cmdDepth.primitive.primitive = &primitive; cmdDepth.primitive.rasterState.culling = mi->getCullingMode(); - - cmdDepth.primitive.skinningHandle = skinning.handle; - cmdDepth.primitive.skinningOffset = skinning.offset; - cmdDepth.primitive.skinningTexture = skinning.handleSampler; - - cmdDepth.primitive.morphWeightBuffer = morphing.handle; cmdDepth.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle(); // FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()? @@ -732,13 +798,13 @@ void RenderPass::updateSummedPrimitiveCounts( // ------------------------------------------------------------------------------------------------ void RenderPass::Executor::overridePolygonOffset(backend::PolygonOffset const* polygonOffset) noexcept { - if ((mPolygonOffsetOverride = (polygonOffset != nullptr))) { + if ((mPolygonOffsetOverride = (polygonOffset != nullptr))) { // NOLINT(*-assignment-in-if-condition) mPolygonOffset = *polygonOffset; } } void RenderPass::Executor::overrideScissor(backend::Viewport const* scissor) noexcept { - if ((mScissorOverride = (scissor != nullptr))) { + if ((mScissorOverride = (scissor != nullptr))) { // NOLINT(*-assignment-in-if-condition) mScissor = *scissor; } } @@ -749,153 +815,236 @@ void RenderPass::Executor::overrideScissor(backend::Viewport const& scissor) noe } void RenderPass::Executor::execute(FEngine& engine, const char*) const noexcept { - execute(engine.getDriverApi(), mCommands.begin(), mCommands.end()); + execute(engine, mCommands.begin(), mCommands.end()); } UTILS_NOINLINE // no need to be inlined -void RenderPass::Executor::execute(backend::DriverApi& driver, +backend::Viewport RenderPass::Executor::applyScissorViewport( + backend::Viewport const& scissorViewport, backend::Viewport const& scissor) noexcept { + // scissor is set, we need to apply the offset/clip + // clang vectorizes this! + constexpr int32_t maxvali = std::numeric_limits::max(); + // compute new left/bottom, assume no overflow + int32_t l = scissor.left + scissorViewport.left; + int32_t b = scissor.bottom + scissorViewport.bottom; + // compute right/top without overflowing, scissor.width/height guaranteed + // to convert to int32 + int32_t r = (l > maxvali - int32_t(scissor.width)) ? maxvali : l + int32_t(scissor.width); + int32_t t = (b > maxvali - int32_t(scissor.height)) ? maxvali : b + int32_t(scissor.height); + // clip to the viewport + l = std::max(l, scissorViewport.left); + b = std::max(b, scissorViewport.bottom); + r = std::min(r, scissorViewport.left + int32_t(scissorViewport.width)); + t = std::min(t, scissorViewport.bottom + int32_t(scissorViewport.height)); + assert_invariant(r >= l && t >= b); + return { l, b, uint32_t(r - l), uint32_t(t - b) }; +} + +UTILS_NOINLINE // no need to be inlined +void RenderPass::Executor::execute(FEngine& engine, const Command* first, const Command* last) const noexcept { + SYSTRACE_CALL(); SYSTRACE_CONTEXT(); + DriverApi& driver = engine.getDriverApi(); + size_t const capacity = engine.getMinCommandBufferSize(); + CircularBuffer const& circularBuffer = driver.getCircularBuffer(); + if (first != last) { SYSTRACE_VALUE32("commandCount", last - first); + bool const scissorOverride = mScissorOverride; + if (UTILS_UNLIKELY(scissorOverride)) { + // initialize with scissor overide + driver.scissor(mScissor); + } + + bool const polygonOffsetOverride = mPolygonOffsetOverride; PipelineState pipeline{ + // initialize with polygon offset override .polygonOffset = mPolygonOffset, - .scissor = mScissor - }, dummyPipeline; - - auto* const pPipelinePolygonOffset = - mPolygonOffsetOverride ? &dummyPipeline.polygonOffset : &pipeline.polygonOffset; + }; - auto* const pScissor = - mScissorOverride ? &dummyPipeline.scissor : &pipeline.scissor; + PipelineState currentPipeline{}; + Handle currentPrimitiveHandle{}; + bool rebindPipeline = true; FMaterialInstance const* UTILS_RESTRICT mi = nullptr; FMaterial const* UTILS_RESTRICT ma = nullptr; auto const* UTILS_RESTRICT pCustomCommands = mCustomCommands.data(); - first--; - while (++first != last) { - assert_invariant(first->key != uint64_t(Pass::SENTINEL)); + // Maximum space occupied in the CircularBuffer by a single `Command`. This must be + // reevaluated when the inner loop below adds DriverApi commands or when we change the + // CommandStream protocol. Currently, the maximum is 240 bytes, and we use 256 to be on + // the safer side. + size_t const maxCommandSizeInBytes = 256; + + // Number of Commands that can be issued and guaranteed to fit in the current + // CircularBuffer allocation. In practice, we'll have tons of headroom especially if + // skinning and morphing aren't used. With a 2 MiB buffer (the default) a batch is + // 8192 commands (i.e. draw calls). + size_t const batchCommandCount = capacity / maxCommandSizeInBytes; + while(first != last) { + Command const* const batchLast = std::min(first + batchCommandCount, last); + + // actual number of commands we need to write (can be smaller than batchCommandCount) + size_t const commandCount = batchLast - first; + size_t const commandSizeInBytes = commandCount * maxCommandSizeInBytes; + + // check we have enough capacity to write these commandCount commands, if not, + // request a new CircularBuffer allocation of `capacity` bytes. + if (UTILS_UNLIKELY(circularBuffer.getUsed() > capacity - commandSizeInBytes)) { + engine.flush(); // TODO: we should use a "fast" flush if possible + } - /* - * Be careful when changing code below, this is the hot inner-loop - */ + first--; + while (++first != batchLast) { + assert_invariant(first->key != uint64_t(Pass::SENTINEL)); - if (UTILS_UNLIKELY((first->key & CUSTOM_MASK) != uint64_t(CustomCommand::PASS))) { - mi = nullptr; // custom command could change the currently bound MaterialInstance - uint32_t const index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT; - assert_invariant(index < mCustomCommands.size()); - pCustomCommands[index](); - continue; - } + /* + * Be careful when changing code below, this is the hot inner-loop + */ - // primitiveHandle may be invalid if no geometry was set on the renderable. - if (UTILS_UNLIKELY(!first->primitive.primitiveHandle)) { - continue; - } + if (UTILS_UNLIKELY((first->key & CUSTOM_MASK) != uint64_t(CustomCommand::PASS))) { + mi = nullptr; // custom command could change the currently bound MaterialInstance + uint32_t const index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT; + assert_invariant(index < mCustomCommands.size()); + pCustomCommands[index](); + continue; + } - // per-renderable uniform - const PrimitiveInfo info = first->primitive; - pipeline.rasterState = info.rasterState; - - if (UTILS_UNLIKELY(mi != info.mi)) { - // this is always taken the first time - mi = info.mi; - ma = mi->getMaterial(); - - auto const& scissor = mi->getScissor(); - if (UTILS_UNLIKELY(mi->hasScissor())) { - // scissor is set, we need to apply the offset/clip - // clang vectorizes this! - constexpr int32_t maxvali = std::numeric_limits::max(); - const backend::Viewport scissorViewport = mScissorViewport; - // compute new left/bottom, assume no overflow - int32_t l = scissor.left + scissorViewport.left; - int32_t b = scissor.bottom + scissorViewport.bottom; - // compute right/top without overflowing, scissor.width/height guaranteed - // to convert to int32 - int32_t r = (l > maxvali - int32_t(scissor.width)) ? - maxvali : l + int32_t(scissor.width); - int32_t t = (b > maxvali - int32_t(scissor.height)) ? - maxvali : b + int32_t(scissor.height); - // clip to the viewport - l = std::max(l, scissorViewport.left); - b = std::max(b, scissorViewport.bottom); - r = std::min(r, scissorViewport.left + int32_t(scissorViewport.width)); - t = std::min(t, scissorViewport.bottom + int32_t(scissorViewport.height)); - assert_invariant(r >= l && t >= b); - *pScissor = { l, b, uint32_t(r - l), uint32_t(t - b) }; - } else { - // no scissor set (common case), 'scissor' has its default value, use that. - *pScissor = scissor; + // primitiveHandle may be invalid if no geometry was set on the renderable. + if (UTILS_UNLIKELY(!first->primitive.primitive->getHwHandle())) { + continue; } - *pPipelinePolygonOffset = mi->getPolygonOffset(); - pipeline.stencilState = mi->getStencilState(); - mi->use(driver); - } + // per-renderable uniform + PrimitiveInfo const info = first->primitive; + pipeline.rasterState = info.rasterState; + pipeline.vertexBufferInfo = info.primitive->getVertexBufferInfoHandle(); + pipeline.primitiveType = info.primitive->getPrimitiveType(); + assert_invariant(pipeline.vertexBufferInfo); + + if (UTILS_UNLIKELY(mi != info.primitive->getMaterialInstance())) { + // this is always taken the first time + mi = info.primitive->getMaterialInstance(); + assert_invariant(mi); + + ma = mi->getMaterial(); + + if (UTILS_LIKELY(!scissorOverride)) { + backend::Viewport scissor = mi->getScissor(); + if (UTILS_UNLIKELY(mi->hasScissor())) { + scissor = applyScissorViewport(mScissorViewport, scissor); + } + driver.scissor(scissor); + } + + if (UTILS_LIKELY(!polygonOffsetOverride)) { + pipeline.polygonOffset = mi->getPolygonOffset(); + } + pipeline.stencilState = mi->getStencilState(); + mi->use(driver); + + // FIXME: MaterialInstance changed (not necessarily the program though), + // however, texture bindings may have changed and currently we need to + // rebind the pipeline when that happens. + rebindPipeline = true; + } - pipeline.program = ma->getProgram(info.materialVariant); + assert_invariant(ma); + pipeline.program = ma->getProgram(info.materialVariant); + + uint16_t const instanceCount = + info.instanceCount & PrimitiveInfo::INSTANCE_COUNT_MASK; + auto getPerObjectUboHandle = + [this, &info, &instanceCount]() -> std::pair, uint32_t> { + if (info.instanceBufferHandle) { + // "hybrid" instancing -- instanceBufferHandle takes the place of the UBO + return { info.instanceBufferHandle, 0 }; + } + bool const userInstancing = + (info.instanceCount & PrimitiveInfo::USER_INSTANCE_MASK) != 0u; + if (!userInstancing && instanceCount > 1) { + // automatic instancing + return { + mInstancedUboHandle, + info.index * sizeof(PerRenderableData) }; + } else { + // manual instancing + return { mUboHandle, info.index * sizeof(PerRenderableData) }; + } + }; + + // Bind per-renderable uniform block. There is no need to attempt to skip this command + // because the backends already do this. + auto const [perObjectUboHandle, offset] = getPerObjectUboHandle(); + assert_invariant(perObjectUboHandle); + driver.bindBufferRange(BufferObjectBinding::UNIFORM, + +UniformBindingPoints::PER_RENDERABLE, + perObjectUboHandle, + offset, + sizeof(PerRenderableUib)); + + if (UTILS_UNLIKELY(info.skinningHandle)) { + // note: we can't bind less than sizeof(PerRenderableBoneUib) due to glsl limitations + driver.bindBufferRange(BufferObjectBinding::UNIFORM, + +UniformBindingPoints::PER_RENDERABLE_BONES, + info.skinningHandle, + info.skinningOffset * sizeof(PerRenderableBoneUib::BoneData), + sizeof(PerRenderableBoneUib)); + // note: always bind the skinningTexture because the shader needs it. + driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING, + info.skinningTexture); + // note: even if only skinning is enabled, binding morphTargetBuffer is needed. + driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING, + info.morphTargetBuffer); + + // FIXME: Currently we need to rebind the PipelineState when texture or + // UBO binding change. + rebindPipeline = true; + } - uint16_t const instanceCount = info.instanceCount & PrimitiveInfo::INSTANCE_COUNT_MASK; - auto getPerObjectUboHandle = - [this, &info, &instanceCount]() -> std::pair, uint32_t> { - if (info.instanceBufferHandle) { - // "hybrid" instancing -- instanceBufferHandle takes the place of the UBO - return { info.instanceBufferHandle, 0 }; + if (UTILS_UNLIKELY(info.morphWeightBuffer)) { + // Instead of using a UBO per primitive, we could also have a single UBO for all + // primitives and use bindUniformBufferRange which might be more efficient. + driver.bindUniformBuffer(+UniformBindingPoints::PER_RENDERABLE_MORPHING, + info.morphWeightBuffer); + driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING, + info.morphTargetBuffer); + // note: even if only morphing is enabled, binding skinningTexture is needed. + driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING, + info.skinningTexture); + + // FIXME: Currently we need to rebind the PipelineState when texture or + // UBO binding change. + rebindPipeline = true; } - bool const userInstancing = - (info.instanceCount & PrimitiveInfo::USER_INSTANCE_MASK) != 0u; - if (!userInstancing && instanceCount > 1) { - // automatic instancing - return { mInstancedUboHandle, info.index * sizeof(PerRenderableData) }; - } else { - // manual instancing - return { mUboHandle, info.index * sizeof(PerRenderableData) }; + + if (rebindPipeline || + (memcmp(&pipeline, ¤tPipeline, sizeof(PipelineState)) != 0)) { + rebindPipeline = false; + currentPipeline = pipeline; + driver.bindPipeline(pipeline); } - }; - - // bind per-renderable uniform block. there is no need to attempt to skip this command - // because the backends already do this. - auto const [perObjectUboHandle, offset] = getPerObjectUboHandle(); - assert_invariant(perObjectUboHandle); - driver.bindBufferRange(BufferObjectBinding::UNIFORM, - +UniformBindingPoints::PER_RENDERABLE, - perObjectUboHandle, - offset, - sizeof(PerRenderableUib)); - - if (UTILS_UNLIKELY(info.skinningHandle)) { - // note: we can't bind less than sizeof(PerRenderableBoneUib) due to glsl limitations - driver.bindBufferRange(BufferObjectBinding::UNIFORM, - +UniformBindingPoints::PER_RENDERABLE_BONES, - info.skinningHandle, - info.skinningOffset * sizeof(PerRenderableBoneUib::BoneData), - sizeof(PerRenderableBoneUib)); - // note: always bind the skinningTexture because the shader needs it. - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING, - info.skinningTexture); - // note: even if only skinning is enabled, binding morphTargetBuffer is needed. - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING, - info.morphTargetBuffer); - } - - if (UTILS_UNLIKELY(info.morphWeightBuffer)) { - // Instead of using a UBO per primitive, we could also have a single UBO for all - // primitives and use bindUniformBufferRange which might be more efficient. - driver.bindUniformBuffer(+UniformBindingPoints::PER_RENDERABLE_MORPHING, - info.morphWeightBuffer); - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING, - info.morphTargetBuffer); - // note: even if only morphing is enabled, binding skinningTexture is needed. - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING, - info.skinningTexture); + + if (info.primitive->getHwHandle() != currentPrimitiveHandle) { + currentPrimitiveHandle = info.primitive->getHwHandle(); + driver.bindRenderPrimitive(info.primitive->getHwHandle()); + } + + driver.draw2( + info.primitive->getIndexOffset(), + info.primitive->getIndexCount(), + instanceCount); } + } - driver.draw(pipeline, info.primitiveHandle, instanceCount); + // If the remaining space is less than half the capacity, we flush right away to + // allow some headroom for commands that might come later. + if (UTILS_UNLIKELY(circularBuffer.getUsed() > capacity / 2)) { + engine.flush(); } } diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 4b671a648f9..fe6e04cafc9 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -22,26 +22,39 @@ #include "details/Camera.h" #include "details/Scene.h" -#include "backend/DriverApiForward.h" - -#include +#include "private/filament/Variant.h" +#include "utils/BitmaskEnum.h" #include #include #include #include +#include #include -#include #include +#include + #include #include +#include +#include +#include #include +#include +#include + namespace filament { +namespace backend { +class CommandBufferQueue; +} + class FMaterialInstance; +class FRenderPrimitive; +class RenderPassBuilder; class RenderPass { public: @@ -171,7 +184,7 @@ class RenderPass { EPILOG = uint64_t(0x2) << CUSTOM_SHIFT }; - enum CommandTypeFlags : uint8_t { + enum class CommandTypeFlags : uint32_t { COLOR = 0x1, // generate the color pass only DEPTH = 0x2, // generate the depth pass only ( e.g. shadowmap) @@ -191,7 +204,6 @@ class RenderPass { SCREEN_SPACE_REFLECTIONS = COLOR | FILTER_TRANSLUCENT_OBJECTS }; - /* * The sorting material key is 32 bits and encoded as: * @@ -224,13 +236,13 @@ class RenderPass { return boolish ? value : uint64_t(0); } - struct PrimitiveInfo { // 48 bytes + struct PrimitiveInfo { // 56 bytes union { - FMaterialInstance const* mi; - uint64_t padding = {}; // ensures mi is 8 bytes on all archs + FRenderPrimitive const* primitive; // 8 bytes; + uint64_t padding = {}; // ensures primitive is 8 bytes on all archs }; // 8 bytes + uint64_t rfu0; // 8 bytes backend::RasterState rasterState; // 4 bytes - backend::Handle primitiveHandle; // 4 bytes backend::Handle skinningHandle; // 4 bytes backend::Handle skinningTexture; // 4 bytes backend::Handle morphWeightBuffer; // 4 bytes @@ -240,20 +252,20 @@ class RenderPass { uint32_t skinningOffset = 0; // 4 bytes uint16_t instanceCount; // 2 bytes [MSb: user] Variant materialVariant; // 1 byte -// uint8_t reserved[0] = {}; // 0 bytes + uint8_t rfu1; // 1 byte + uint32_t rfu2; // 4 byte static const uint16_t USER_INSTANCE_MASK = 0x8000u; static const uint16_t INSTANCE_COUNT_MASK = 0x7fffu; }; - static_assert(sizeof(PrimitiveInfo) == 48); + static_assert(sizeof(PrimitiveInfo) == 56); struct alignas(8) Command { // 64 bytes CommandKey key = 0; // 8 bytes - PrimitiveInfo primitive; // 48 bytes - uint64_t reserved[1] = {}; // 8 bytes + PrimitiveInfo primitive; // 56 bytes bool operator < (Command const& rhs) const noexcept { return key < rhs.key; } // placement new declared as "throw" to avoid the compiler's null-check - inline void* operator new (std::size_t, void* ptr) { + inline void* operator new (size_t, void* ptr) { assert_invariant(ptr); return ptr; } @@ -263,67 +275,37 @@ 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< - utils::LinearAllocator, // note: can't change this allocator + utils::LinearAllocatorWithFallback, utils::LockingPolicy::NoLock, utils::TrackingPolicy::HighWatermark, utils::AreaPolicy::StaticArea>; - /* - * Create a RenderPass. - * The Arena is used to allocate commands which are then owned by the Arena. - */ - RenderPass(FEngine& engine, Arena& arena) noexcept; + // RenderPass can only be moved + RenderPass(RenderPass&& rhs) = default; - // Copy the RenderPass as is. This can be used to create a RenderPass from a "template" - // by copying from an "empty" RenderPass. - RenderPass(RenderPass const& rhs); + // RenderPass can't be copied + RenderPass(RenderPass const& rhs) = delete; + RenderPass& operator=(RenderPass const& rhs) = delete; + RenderPass& operator=(RenderPass&& rhs) = delete; // allocated commands ARE NOT freed, they're owned by the Arena ~RenderPass() noexcept; - // a box that both offsets the viewport and clips it - void setScissorViewport(backend::Viewport viewport) noexcept; - - // specifies the geometry to generate commands for - void setGeometry(FScene::RenderableSoa const& soa, utils::Range vr, - backend::Handle uboHandle) noexcept; - - // specifies camera information (e.g. used for sorting commands) - void setCamera(const CameraInfo& camera) noexcept; - - // flags controlling how commands are generated - void setRenderFlags(RenderFlags flags) noexcept { mFlags = flags; } - RenderFlags getRenderFlags() const noexcept { return mFlags; } - - // variant to use - void setVariant(Variant variant) noexcept { mVariant = variant; } - - // Sets the visibility mask, which is AND-ed against each Renderable's VISIBLE_MASK to determine - // if the renderable is visible for this pass. - // Defaults to all 1's, which means all renderables in this render pass will be rendered. - void setVisibilityMask(FScene::VisibleMaskType mask) noexcept { mVisibilityMask = mask; } - Command const* begin() const noexcept { return mCommandBegin; } Command const* end() const noexcept { return mCommandEnd; } bool empty() const noexcept { return begin() == end(); } - // This is the main function of this class, this appends commands to the pass using - // the current camera, geometry and flags set. This can be called multiple times if needed. - void appendCommands(FEngine& engine, CommandTypeFlags commandTypeFlags) noexcept; - - // sorts and instanceify commands then trims sentinels - void sortCommands(FEngine& engine) noexcept; - // Helper to execute all the commands generated by this RenderPass - void execute(FEngine& engine, const char* name, + static void execute(RenderPass const& pass, + FEngine& engine, const char* name, backend::Handle renderTarget, - backend::RenderPassParams params) const noexcept; + backend::RenderPassParams params) noexcept; /* * Executor holds the range of commands to execute for a given pass @@ -331,6 +313,7 @@ class RenderPass { class Executor { using CustomCommandFn = std::function; friend class RenderPass; + friend class RenderPassBuilder; // these fields are constant after creation utils::Slice mCommands; @@ -346,8 +329,11 @@ class RenderPass { Executor(RenderPass const* pass, Command const* b, Command const* e) noexcept; - void execute(backend::DriverApi& driver, - const Command* first, const Command* last) const noexcept; + void execute(FEngine& engine, const Command* first, const Command* last) const noexcept; + + static backend::Viewport applyScissorViewport( + backend::Viewport const& scissorViewport, + backend::Viewport const& scissor) noexcept; public: Executor() = default; @@ -366,37 +352,39 @@ class RenderPass { }; // returns a new executor for this pass - Executor getExecutor() { - return { this, mCommandBegin, mCommandEnd }; - } - Executor getExecutor() const { return { this, mCommandBegin, mCommandEnd }; } - // returns a new executor for this pass with a custom range - Executor getExecutor(Command const* b, Command const* e) { - return { this, b, e }; - } - Executor getExecutor(Command const* b, Command const* e) const { return { this, b, e }; } +private: + friend class FRenderer; + friend class RenderPassBuilder; + RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexcept; + + // This is the main function of this class, this appends commands to the pass using + // the current camera, geometry and flags set. This can be called multiple times if needed. + void appendCommands(FEngine& engine, + utils::Slice commands, CommandTypeFlags commandTypeFlags) noexcept; + // Appends a custom command. - void appendCustomCommand(uint8_t channel, Pass pass, CustomCommand custom, uint32_t order, + void appendCustomCommand(Command* commands, + uint8_t channel, Pass pass, CustomCommand custom, uint32_t order, Executor::CustomCommandFn command); + void resize(Arena& arena, size_t count) noexcept; -private: - friend class FRenderer; + // sorts commands then trims sentinels + void sortCommands(Arena& arena) noexcept; - Command* append(size_t count) noexcept; - void resize(size_t count) noexcept; - void instanceify(FEngine& engine) noexcept; + // instanceify commands then trims sentinels + void instanceify(FEngine& engine, Arena& arena) noexcept; - // we choose the command count per job to minimize JobSystem overhead. - // on a Pixel 4, 2048 commands is about half a millisecond of processing. + // We choose the command count per job to minimize JobSystem overhead. + // On a Pixel 4, 2048 commands is about half a millisecond of processing. static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 2048; static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_SIZE = sizeof(Command) * JOBS_PARALLEL_FOR_COMMANDS_COUNT; @@ -404,17 +392,19 @@ class RenderPass { static_assert(JOBS_PARALLEL_FOR_COMMANDS_SIZE % utils::CACHELINE_SIZE == 0, "Size of Commands jobs must be multiple of a cache-line size"); - static inline void generateCommands(uint32_t commandTypeFlags, Command* commands, + static inline void generateCommands(CommandTypeFlags commandTypeFlags, Command* commands, FScene::RenderableSoa const& soa, utils::Range range, Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, - math::float3 cameraPosition, math::float3 cameraForward) noexcept; + math::float3 cameraPosition, math::float3 cameraForward, + uint8_t instancedStereoEyeCount) noexcept; - template - static inline Command* generateCommandsImpl(uint32_t extraFlags, Command* curr, + template + static inline Command* generateCommandsImpl(RenderPass::CommandTypeFlags extraFlags, Command* curr, FScene::RenderableSoa const& soa, utils::Range range, Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, - math::float3 cameraPosition, math::float3 cameraForward) noexcept; + math::float3 cameraPosition, math::float3 cameraForward, + uint8_t instancedStereoEyeCount) noexcept; static void setupColorCommand(Command& cmdDraw, Variant variant, FMaterialInstance const* mi, bool inverseFrontFaces) noexcept; @@ -422,50 +412,128 @@ class RenderPass { static void updateSummedPrimitiveCounts( FScene::RenderableSoa& renderableData, utils::Range vr) noexcept; - // a reference to the Engine, mostly to get to things like JobSystem - // Arena where all Commands are allocated. The Arena owns the commands. - Arena& mCommandArena; + FScene::RenderableSoa const& mRenderableSoa; + utils::Range const mVisibleRenderables; + backend::Handle const mUboHandle; + math::float3 const mCameraPosition; + math::float3 const mCameraForwardVector; + RenderFlags const mFlags; + Variant const mVariant; + FScene::VisibleMaskType const mVisibilityMask; + backend::Viewport const mScissorViewport; // Pointer to the first command Command* mCommandBegin = nullptr; - // Pointer to one past the last command Command* mCommandEnd = nullptr; + // a UBO for instanced primitives + backend::Handle mInstancedUboHandle; + // a vector for our custom commands + using CustomCommandVector = std::vector>; + mutable CustomCommandVector mCustomCommands; +}; - // the SOA containing the renderables we're interested in - FScene::RenderableSoa const* mRenderableSoa = nullptr; +class RenderPassBuilder { + friend class RenderPass; - // The range of visible renderables in the SOA above + RenderPass::Arena& mArena; + RenderPass::CommandTypeFlags mCommandTypeFlags{}; + backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX }; + FScene::RenderableSoa const* mRenderableSoa = nullptr; utils::Range mVisibleRenderables{}; - - // the UBO containing the data for the renderables backend::Handle mUboHandle; - backend::Handle mInstancedUboHandle; - - // info about the camera math::float3 mCameraPosition{}; math::float3 mCameraForwardVector{}; + RenderPass::RenderFlags mFlags{}; + Variant mVariant{}; + FScene::VisibleMaskType mVisibilityMask = std::numeric_limits::max(); - // info about the scene features (e.g.: has shadows, lighting, etc...) - RenderFlags mFlags{}; + using CustomCommandRecord = std::tuple< + uint8_t, + RenderPass::Pass, + RenderPass::CustomCommand, + uint32_t, + RenderPass::Executor::CustomCommandFn>; - // Variant to use - Variant mVariant{}; + using CustomCommandContainer = std::vector>; - // Additional visibility mask - FScene::VisibleMaskType mVisibilityMask = std::numeric_limits::max(); + // we make this optional because it's not used often, and we don't want to have + // to construct it by default. + std::optional mCustomCommands; - backend::Viewport mScissorViewport{ 0, 0, - std::numeric_limits::max(), - std::numeric_limits::max() }; +public: + explicit RenderPassBuilder(RenderPass::Arena& arena) : mArena(arena) { } - // a vector for our custom commands - using CustomCommandVector = std::vector>; - mutable CustomCommandVector mCustomCommands; + RenderPassBuilder& commandTypeFlags(RenderPass::CommandTypeFlags commandTypeFlags) noexcept { + mCommandTypeFlags = commandTypeFlags; + return *this; + } + + RenderPassBuilder& scissorViewport(backend::Viewport viewport) noexcept { + mScissorViewport = viewport; + return *this; + } + + // specifies the geometry to generate commands for + RenderPassBuilder& geometry(FScene::RenderableSoa const& soa, utils::Range vr, + backend::Handle uboHandle) noexcept { + mRenderableSoa = &soa; + mVisibleRenderables = vr; + mUboHandle = uboHandle; + return *this; + } + + // Specifies camera information (e.g. used for sorting commands) + RenderPassBuilder& camera(const CameraInfo& camera) noexcept { + mCameraPosition = camera.getPosition(); + mCameraForwardVector = camera.getForwardVector(); + return *this; + } + + // flags controlling how commands are generated + RenderPassBuilder& renderFlags(RenderPass::RenderFlags flags) noexcept { + mFlags = flags; + return *this; + } + + // like above but allows to set specific flags + RenderPassBuilder& renderFlags( + RenderPass::RenderFlags mask, RenderPass::RenderFlags value) noexcept { + mFlags = (mFlags & mask) | (value & mask); + return *this; + } + + // variant to use + RenderPassBuilder& variant(Variant variant) noexcept { + mVariant = variant; + return *this; + } + + // Sets the visibility mask, which is AND-ed against each Renderable's VISIBLE_MASK to + // determine if the renderable is visible for this pass. + // Defaults to all 1's, which means all renderables in this render pass will be rendered. + RenderPassBuilder& visibilityMask(FScene::VisibleMaskType mask) noexcept { + mVisibilityMask = mask; + return *this; + } + + RenderPassBuilder& customCommand(FEngine& engine, + uint8_t channel, + RenderPass::Pass pass, + RenderPass::CustomCommand custom, + uint32_t order, + const RenderPass::Executor::CustomCommandFn& command); + + RenderPass build(FEngine& engine); }; + } // namespace filament +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; + #endif // TNT_FILAMENT_RENDERPASS_H diff --git a/filament/src/RenderPrimitive.cpp b/filament/src/RenderPrimitive.cpp index 88d95380b22..8bd5e5ad573 100644 --- a/filament/src/RenderPrimitive.cpp +++ b/filament/src/RenderPrimitive.cpp @@ -16,13 +16,20 @@ #include "RenderPrimitive.h" -#include "details/Engine.h" +#include +#include + #include "details/IndexBuffer.h" -#include "details/Material.h" +#include "details/MaterialInstance.h" #include "details/VertexBuffer.h" +#include +#include + #include +#include + namespace filament { void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver, @@ -36,17 +43,7 @@ void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverAp if (entry.indices && entry.vertices) { FVertexBuffer* vertexBuffer = downcast(entry.vertices); FIndexBuffer* indexBuffer = downcast(entry.indices); - - AttributeBitset enabledAttributes = vertexBuffer->getDeclaredAttributes(); - - auto const& ebh = vertexBuffer->getHwHandle(); - auto const& ibh = indexBuffer->getHwHandle(); - - mHandle = factory.create(driver, ebh, ibh, entry.type, (uint32_t)entry.offset, - (uint32_t)entry.minIndex, (uint32_t)entry.maxIndex, (uint32_t)entry.count); - - mPrimitiveType = entry.type; - mEnabledAttributes = enabledAttributes; + set(factory, driver, entry.type, vertexBuffer, indexBuffer, entry.offset, entry.count); } } @@ -58,22 +55,23 @@ void FRenderPrimitive::terminate(HwRenderPrimitiveFactory& factory, backend::Dri void FRenderPrimitive::set(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver, RenderableManager::PrimitiveType type, - FVertexBuffer* vertices, FIndexBuffer* indices, size_t offset, - size_t minIndex, size_t maxIndex, size_t count) noexcept { - - AttributeBitset enabledAttributes = vertices->getDeclaredAttributes(); - - auto const& ebh = vertices->getHwHandle(); - auto const& ibh = indices->getHwHandle(); - + FVertexBuffer* vertexBuffer, FIndexBuffer* indexBuffer, + size_t offset, size_t count) noexcept { if (mHandle) { factory.destroy(driver, mHandle); } - mHandle = factory.create(driver, ebh, ibh, type, - (uint32_t)offset, (uint32_t)minIndex, (uint32_t)maxIndex, (uint32_t)count); + AttributeBitset const enabledAttributes = vertexBuffer->getDeclaredAttributes(); + + auto const& ebh = vertexBuffer->getHwHandle(); + auto const& ibh = indexBuffer->getHwHandle(); + + mHandle = factory.create(driver, ebh, ibh, type); + mVertexBufferInfoHandle = vertexBuffer->getVertexBufferInfoHandle(); mPrimitiveType = type; + mIndexOffset = offset; + mIndexCount = count; mEnabledAttributes = enabledAttributes; } diff --git a/filament/src/RenderPrimitive.h b/filament/src/RenderPrimitive.h index 7e77c13cb15..07d7431f021 100644 --- a/filament/src/RenderPrimitive.h +++ b/filament/src/RenderPrimitive.h @@ -17,13 +17,16 @@ #ifndef TNT_FILAMENT_DETAILS_RENDERPRIMITIVE_H #define TNT_FILAMENT_DETAILS_RENDERPRIMITIVE_H +#include + #include "components/RenderableManager.h" #include "details/MaterialInstance.h" +#include #include -#include +#include namespace filament { @@ -42,14 +45,18 @@ class FRenderPrimitive { void set(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver, RenderableManager::PrimitiveType type, - FVertexBuffer* vertices, FIndexBuffer* indices, size_t offset, - size_t minIndex, size_t maxIndex, size_t count) noexcept; + FVertexBuffer* vertexBuffer, FIndexBuffer* indexBuffer, size_t offset, + size_t count) noexcept; // frees driver resources, object becomes invalid void terminate(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver); const FMaterialInstance* getMaterialInstance() const noexcept { return mMaterialInstance; } - backend::Handle getHwHandle() const noexcept { return mHandle; } + backend::RenderPrimitiveHandle getHwHandle() const noexcept { return mHandle; } + backend::VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; } + uint32_t getIndexOffset() const noexcept { return mIndexOffset; } + uint32_t getIndexCount() const noexcept { return mIndexCount; } + backend::PrimitiveType getPrimitiveType() const noexcept { return mPrimitiveType; } AttributeBitset getEnabledAttributes() const noexcept { return mEnabledAttributes; } uint16_t getBlendOrder() const noexcept { return mBlendOrder; } @@ -66,13 +73,19 @@ class FRenderPrimitive { } private: - FMaterialInstance const* mMaterialInstance = nullptr; - backend::Handle mHandle = {}; + // These first fields are dereferences from PrimitiveInfo, keep them together + struct { + FMaterialInstance const* mMaterialInstance = nullptr; + backend::Handle mHandle = {}; + backend::Handle mVertexBufferInfoHandle = {}; + uint32_t mIndexOffset = 0; + uint32_t mIndexCount = 0; + }; + AttributeBitset mEnabledAttributes = {}; uint16_t mBlendOrder = 0; bool mGlobalBlendOrderEnabled = false; backend::PrimitiveType mPrimitiveType = backend::PrimitiveType::TRIANGLES; - UTILS_UNUSED uint8_t reserved[4] = {}; }; } // namespace filament diff --git a/filament/src/RenderableManager.cpp b/filament/src/RenderableManager.cpp index bc1240415d1..133dd817c2c 100644 --- a/filament/src/RenderableManager.cpp +++ b/filament/src/RenderableManager.cpp @@ -31,6 +31,22 @@ bool RenderableManager::hasComponent(utils::Entity e) const noexcept { return downcast(this)->hasComponent(e); } +size_t RenderableManager::getComponentCount() const noexcept { + return downcast(this)->getComponentCount(); +} + +bool RenderableManager::empty() const noexcept { + return downcast(this)->empty(); +} + +utils::Entity RenderableManager::getEntity(RenderableManager::Instance i) const noexcept { + return downcast(this)->getEntity(i); +} + +utils::Entity const* RenderableManager::getEntities() const noexcept { + return downcast(this)->getEntities(); +} + RenderableManager::Instance RenderableManager::getInstance(utils::Entity e) const noexcept { return downcast(this)->getInstance(e); @@ -40,7 +56,7 @@ void RenderableManager::destroy(utils::Entity e) noexcept { return downcast(this)->destroy(e); } -void RenderableManager::setAxisAlignedBoundingBox(Instance instance, const Box& aabb) noexcept { +void RenderableManager::setAxisAlignedBoundingBox(Instance instance, const Box& aabb) { downcast(this)->setAxisAlignedBoundingBox(instance, aabb); } diff --git a/filament/src/RendererUtils.cpp b/filament/src/RendererUtils.cpp index e45ce8c7d97..3e1cafdbc9d 100644 --- a/filament/src/RendererUtils.cpp +++ b/filament/src/RendererUtils.cpp @@ -16,17 +16,35 @@ #include "RendererUtils.h" +#include "PostProcessManager.h" + #include "details/Engine.h" #include "details/View.h" #include "fg/FrameGraph.h" #include "fg/FrameGraphId.h" #include "fg/FrameGraphResources.h" +#include "fg/FrameGraphTexture.h" + +#include +#include +#include + +#include +#include +#include +#include #include #include #include +#include +#include + +#include +#include + namespace filament { using namespace backend; @@ -57,6 +75,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"); @@ -89,7 +108,7 @@ FrameGraphId RendererUtils::colorPass( data.color = builder.createTexture("Color Buffer", colorBufferDesc); } - const bool canResolveDepth = engine.getDriverApi().isAutoDepthResolveSupported(); + const bool canAutoResolveDepth = engine.getDriverApi().isAutoDepthResolveSupported(); if (!data.depth) { // clear newly allocated depth/stencil buffers, regardless of given clear flags @@ -100,7 +119,7 @@ FrameGraphId RendererUtils::colorPass( "Depth/Stencil Buffer" : "Depth Buffer"; bool const isES2 = - engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; + engine.getDriverApi().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0; TextureFormat const stencilFormat = isES2 ? TextureFormat::DEPTH24_STENCIL8 : TextureFormat::DEPTH32F_STENCIL8; @@ -114,13 +133,19 @@ FrameGraphId RendererUtils::colorPass( data.depth = builder.createTexture(name, { .width = colorBufferDesc.width, .height = colorBufferDesc.height, - // If the color attachment requested MS, we assume this means the MS buffer - // must be kept, and for that reason we allocate the depth buffer with MS - // as well. On the other hand, if the color attachment was allocated without - // MS, no need to allocate the depth buffer with MS, if the RT is MS, - // the tile depth buffer will be MS, but it'll be resolved to single - // sample automatically -- which is what we want. - .samples = canResolveDepth ? colorBufferDesc.samples : uint8_t(config.msaa), + // If the color attachment requested MS, we assume this means the MS + // buffer must be kept, and for that reason we allocate the depth + // buffer with MS as well. + // On the other hand, if the color attachment was allocated without + // MS, no need to allocate the depth buffer with MS; Either it's not + // multi-sampled or it is auto-resolved. + // One complication above is that some backends don't support + // 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) { @@ -147,6 +172,9 @@ FrameGraphId RendererUtils::colorPass( 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) { + layerCount = engine.getConfig().stereoscopicEyeCount; + } /* * There is a bit of magic happening here regarding the viewport used. @@ -168,7 +196,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, @@ -193,8 +222,8 @@ FrameGraphId RendererUtils::colorPass( TextureHandle const ssr = data.ssr ? resources.getTexture(data.ssr) : engine.getOneTextureArray(); - view.prepareSSR(ssr, config.ssrLodOffset, - view.getScreenSpaceReflectionsOptions()); + view.prepareSSR(ssr, config.screenSpaceReflectionHistoryNotReady, + config.ssrLodOffset, view.getScreenSpaceReflectionsOptions()); // Note: here we can't use data.color's descriptor for the viewport because // the actual viewport might be offset when the target is the swapchain. @@ -224,10 +253,6 @@ FrameGraphId RendererUtils::colorPass( out.params.subpassMask = 1; } - // this is a good time to flush the CommandStream, because we're about to potentially - // output a lot of commands. This guarantees here that we have at least - // FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB bytes (1MiB by default). - engine.flush(); driver.beginRenderPass(out.target, out.params); passExecutor.execute(engine, resources.getPassName()); driver.endRenderPass(); @@ -282,7 +307,7 @@ std::pair, bool> RendererUtils::refractionPass( input = RendererUtils::colorPass(fg, "Color Pass (opaque)", engine, view, { // When rendering the opaques, we need to conserve the sample buffer, - // so create config that specifies the sample count. + // so create a config that specifies the sample count. .width = config.physicalViewport.width, .height = config.physicalViewport.height, .samples = config.msaa, @@ -312,11 +337,11 @@ std::pair, bool> RendererUtils::refractionPass( config, colorGradingConfig, pass.getExecutor(refraction, pass.end())); if (config.msaa > 1 && !colorGradingConfig.asSubpass) { - // We need to do a resolve here because later passes (such as color grading or DoF) will need - // to sample from 'output'. However, because we have MSAA, we know we're not sampleable. - // And this is because in the SSR case, we had to use a renderbuffer to conserve the - // multi-sample buffer. - output = ppm.resolveBaseLevel(fg, "Resolved Color Buffer", output); + // We need to do a resolve here because later passes (such as color grading or DoF) will + // need to sample from 'output'. However, because we have MSAA, we know we're not + // sampleable. And this is because in the SSR case, we had to use a renderbuffer to + // conserve the multi-sample buffer. + output = ppm.resolve(fg, "Resolved Color Buffer", output, { .levels = 1 }); } } else { output = input; diff --git a/filament/src/RendererUtils.h b/filament/src/RendererUtils.h index fa2efbbd9b1..0a5f7b2808b 100644 --- a/filament/src/RendererUtils.h +++ b/filament/src/RendererUtils.h @@ -67,6 +67,8 @@ class RendererUtils { bool hasScreenSpaceReflectionsOrRefractions; // Use a depth format with a stencil component. bool enabledStencilBuffer; + // whether the screenspace reflections history buffer is initialized + bool screenSpaceReflectionHistoryNotReady; }; static FrameGraphId colorPass( diff --git a/filament/src/ResourceAllocator.cpp b/filament/src/ResourceAllocator.cpp index 06d21a3de3c..2990629bcea 100644 --- a/filament/src/ResourceAllocator.cpp +++ b/filament/src/ResourceAllocator.cpp @@ -16,15 +16,30 @@ #include "ResourceAllocator.h" -#include "private/backend/DriverApi.h" +#include #include "details/Texture.h" +#include +#include +#include +#include + +#include "private/backend/DriverApi.h" + +#include +#include #include #include -#include +#include +#include +#include #include +#include + +#include +#include using namespace utils; @@ -42,8 +57,7 @@ ResourceAllocator::AssociativeContainer::AssociativeContainer() { template UTILS_NOINLINE -ResourceAllocator::AssociativeContainer::~AssociativeContainer() noexcept { -} +ResourceAllocator::AssociativeContainer::~AssociativeContainer() noexcept = default; template UTILS_NOINLINE @@ -78,9 +92,9 @@ void ResourceAllocator::AssociativeContainer::emplace(ARGS&& ... args) ResourceAllocatorInterface::~ResourceAllocatorInterface() = default; size_t ResourceAllocator::TextureKey::getSize() const noexcept { - size_t pixelCount = width * height * depth; + size_t const pixelCount = width * height * depth; size_t size = pixelCount * FTexture::getFormatSize(format); - size_t s = std::max(uint8_t(1), samples); + size_t const s = std::max(uint8_t(1), samples); if (s > 1) { // if we have MSAA, we assume N times the storage size *= s; @@ -94,8 +108,9 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept { return size; } -ResourceAllocator::ResourceAllocator(DriverApi& driverApi) noexcept - : mBackend(driverApi) { +ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept + : mCacheMaxAge(config.resourceAllocatorCacheMaxAge), + mBackend(driverApi) { } ResourceAllocator::~ResourceAllocator() noexcept { @@ -112,12 +127,12 @@ void ResourceAllocator::terminate() noexcept { } } -RenderTargetHandle ResourceAllocator::createRenderTarget(const char* name, +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 { @@ -181,7 +196,7 @@ void ResourceAllocator::destroyTexture(TextureHandle h) noexcept { // move it to the cache const TextureKey key = it->second; - uint32_t size = key.getSize(); + uint32_t const size = key.getSize(); mTextureCache.emplace(key, TextureCachePayload{ h, mAge, size }); mCacheSize += size; @@ -202,51 +217,18 @@ void ResourceAllocator::gc() noexcept { // Purging strategy: // - remove entries that are older than a certain age // - remove only one entry per gc(), - // - unless we're at capacity - // - remove LRU entries until we're below capacity auto& textureCache = mTextureCache; for (auto it = textureCache.begin(); it != textureCache.end();) { const size_t ageDiff = age - it->second.age; - if (ageDiff >= CACHE_MAX_AGE) { - it = purge(it); - if (mCacheSize < CACHE_CAPACITY) { - // if we're not at capacity, only purge a single entry per gc, trying to - // avoid a burst of work. - break; - } + if (ageDiff >= mCacheMaxAge) { + purge(it); + // only purge a single entry per gc + break; } else { ++it; } } - - if (UTILS_UNLIKELY(mCacheSize >= CACHE_CAPACITY)) { - // make a copy of our CacheContainer to a vector - using Vector = FixedCapacityVector>; - auto cache = Vector::with_capacity(textureCache.size()); - std::copy(textureCache.begin(), textureCache.end(), std::back_insert_iterator(cache)); - - // sort by least recently used - std::sort(cache.begin(), cache.end(), [](auto const& lhs, auto const& rhs) { - return lhs.second.age < rhs.second.age; - }); - - // now remove entries until we're at capacity - auto curr = cache.begin(); - while (mCacheSize >= CACHE_CAPACITY) { - // by construction this entry must exist - purge(textureCache.find(curr->first)); - ++curr; - } - - // Since we're sorted already, reset the oldestAge of the whole system - size_t oldestAge = cache.front().second.age; - for (auto& it : textureCache) { - it.second.age -= oldestAge; - } - mAge -= oldestAge; - } - //if (mAge % 60 == 0) dump(); } UTILS_NOINLINE @@ -264,12 +246,12 @@ void ResourceAllocator::dump(bool brief) const noexcept { } } -ResourceAllocator::CacheContainer::iterator ResourceAllocator::purge( +void ResourceAllocator::purge( ResourceAllocator::CacheContainer::iterator const& pos) { //slog.d << "purging " << pos->second.handle.getId() << ", age=" << pos->second.age << io::endl; mBackend.destroyTexture(pos->second.handle); mCacheSize -= pos->second.size; - return mTextureCache.erase(pos); + mTextureCache.erase(pos); } } // namespace filament diff --git a/filament/src/ResourceAllocator.h b/filament/src/ResourceAllocator.h index d16a046c6c1..5486592fa53 100644 --- a/filament/src/ResourceAllocator.h +++ b/filament/src/ResourceAllocator.h @@ -17,6 +17,8 @@ #ifndef TNT_FILAMENT_RESOURCEALLOCATOR_H #define TNT_FILAMENT_RESOURCEALLOCATOR_H +#include + #include #include #include @@ -27,7 +29,9 @@ #include #include +#include +#include #include namespace filament { @@ -41,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; @@ -62,7 +67,8 @@ class ResourceAllocatorInterface { class ResourceAllocator final : public ResourceAllocatorInterface { public: - explicit ResourceAllocator(backend::DriverApi& driverApi) noexcept; + explicit ResourceAllocator( + Engine::Config const& config, backend::DriverApi& driverApi) noexcept; ~ResourceAllocator() noexcept override; void terminate() noexcept; @@ -72,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; @@ -89,9 +96,7 @@ class ResourceAllocator final : public ResourceAllocatorInterface { void gc() noexcept; private: - // TODO: these should be settings of the engine - static constexpr size_t CACHE_CAPACITY = 64u << 20u; // 64 MiB - static constexpr size_t CACHE_MAX_AGE = 30u; + size_t const mCacheMaxAge; struct TextureKey { const char* name; // doesn't participate in the hash @@ -190,7 +195,7 @@ class ResourceAllocator final : public ResourceAllocatorInterface { using CacheContainer = AssociativeContainer; using InUseContainer = AssociativeContainer; - CacheContainer::iterator purge(CacheContainer::iterator const& pos); + void purge(ResourceAllocator::CacheContainer::iterator const& pos); backend::DriverApi& mBackend; CacheContainer mTextureCache; diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index e686824ed91..1cc7496723f 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -55,6 +55,10 @@ void Scene::removeEntities(const Entity* entities, size_t count) { downcast(this)->removeEntities(entities, count); } +size_t Scene::getEntityCount() const noexcept { + return downcast(this)->getEntityCount(); +} + size_t Scene::getRenderableCount() const noexcept { return downcast(this)->getRenderableCount(); } diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index e2b8dc769e7..7015e14466f 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -16,19 +16,39 @@ #include "ShadowMap.h" -#include "RenderPass.h" +#include +#include +#include #include "components/LightManager.h" +#include "details/DebugRegistry.h" #include "details/Engine.h" #include "details/Scene.h" +#include #include +#include #include +#include +#include #include +#include +#include +#include +#include +#include + +#include +#include +#include #include +#include + +#include +#include using namespace utils; @@ -108,11 +128,13 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine, ShadowMapInfo const& shadowMapInfo, SceneInfo const& sceneInfo) noexcept { + // reset the visible shadow status + mHasVisibleShadows = false; + FLightManager const& lcm = engine.getLightManager(); FLightManager::Instance const li = lightData.elementAt(index); FLightManager::ShadowParams const params = lcm.getShadowParams(li); - // We can't use LISPSM in stable mode const auto direction = lightData.elementAt(index); auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] = @@ -137,6 +159,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine, */ mat4f W, Wp, L; + // We can't use LISPSM in stable mode const bool useLispsm = params.options.lispsm && !params.options.stable; if (useLispsm) { // Orient the shadow map in the direction of the view vector by constructing a @@ -236,7 +259,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine, ShadowMap::ShaderParameters ShadowMap::updatePunctual( mat4f const& Mv, float outerConeAngle, float nearPlane, float farPlane, const ShadowMapInfo& shadowMapInfo, const FLightManager::ShadowParams& params) noexcept { - const mat4f Mp = mat4f::perspective(outerConeAngle * f::RAD_TO_DEG * 2.0f, 1.0f, nearPlane, farPlane); + const mat4f Mp = mat4f::perspective( + outerConeAngle * f::RAD_TO_DEG * 2.0f, 1.0f, nearPlane, farPlane); assert_invariant(shadowMapInfo.textureDimension == mOptions->mapSize); diff --git a/filament/src/ShadowMap.h b/filament/src/ShadowMap.h index b1678503dd6..d0ca26945f9 100644 --- a/filament/src/ShadowMap.h +++ b/filament/src/ShadowMap.h @@ -17,20 +17,30 @@ #ifndef TNT_FILAMENT_DETAILS_SHADOWMAP_H #define TNT_FILAMENT_DETAILS_SHADOWMAP_H -#include "components/LightManager.h" +#include +#include "Culler.h" #include "PerShadowMapUniforms.h" #include "details/Camera.h" #include "details/Scene.h" +#include "components/LightManager.h" + #include +#include -#include -#include +#include -#include +#include +#include #include +#include + +#include + +#include +#include namespace filament { @@ -163,7 +173,7 @@ class ShadowMap { FCamera const& getCamera() const noexcept { return *mCamera; } // use only for debugging - FCamera const& getDebugCamera() const noexcept { return *mDebugCamera; } + FCamera const* getDebugCamera() const noexcept { return mDebugCamera; } // Update SceneInfo struct for a given light static void updateSceneInfoDirectional(const math::mat4f& Mv, FScene const& scene, diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 73a51b16c5d..4f971450fbe 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -15,34 +15,55 @@ */ #include "ShadowMapManager.h" - #include "RenderPass.h" #include "ShadowMap.h" +#include +#include +#include + +#include + +#include "components/RenderableManager.h" + +#include "details/Camera.h" +#include "details/DebugRegistry.h" #include "details/Texture.h" #include "details/View.h" -#include +#include "fg/FrameGraph.h" +#include "fg/FrameGraphId.h" +#include "fg/FrameGraphRenderPass.h" +#include "fg/FrameGraphTexture.h" + +#include +#include -#include #include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include namespace filament { using namespace backend; using namespace math; -ShadowMapManager::ShadowMapManager(FEngine& engine) - : mEngine(engine) { - // initialize our ShadowMap array in-place - UTILS_NOUNROLL - for (auto& entry : mShadowMapCache) { - new (&entry) ShadowMap(engine); - } - - mShadowUbh = engine.getDriverApi().createBufferObject(mShadowUb.getSize(), - BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC); - +ShadowMapManager::ShadowMapManager(FEngine& engine) { FDebugRegistry& debugRegistry = engine.getDebugRegistry(); debugRegistry.registerProperty("d.shadowmap.visualize_cascades", &engine.debug.shadowmap.visualize_cascades); @@ -52,25 +73,75 @@ ShadowMapManager::ShadowMapManager(FEngine& engine) ShadowMapManager::~ShadowMapManager() { // destroy the ShadowMap array in-place - UTILS_NOUNROLL - for (auto& entry : mShadowMapCache) { - std::destroy_at(std::launder(reinterpret_cast(&entry))); + if (UTILS_UNLIKELY(mInitialized)) { + UTILS_NOUNROLL + for (auto& entry: mShadowMapCache) { + std::destroy_at(std::launder(reinterpret_cast(&entry))); + } } } -void ShadowMapManager::terminate(FEngine& engine) { - DriverApi& driver = engine.getDriverApi(); - driver.destroyBufferObject(mShadowUbh); - UTILS_NOUNROLL - for (auto& entry : mShadowMapCache) { - std::launder(reinterpret_cast(&entry))->terminate(engine); +void ShadowMapManager::createIfNeeded(FEngine& engine, + std::unique_ptr& inOutShadowMapManager) { + if (UTILS_UNLIKELY(!inOutShadowMapManager)) { + inOutShadowMapManager.reset(new ShadowMapManager(engine)); + } +} + +void ShadowMapManager::terminate(FEngine& engine, + std::unique_ptr& shadowMapManager) { + if (shadowMapManager) { + shadowMapManager->terminate(engine); } } +void ShadowMapManager::terminate(FEngine& engine) { + if (UTILS_UNLIKELY(mInitialized)) { + DriverApi& driver = engine.getDriverApi(); + driver.destroyBufferObject(mShadowUbh); + UTILS_NOUNROLL + for (auto& entry: mShadowMapCache) { + std::launder(reinterpret_cast(&entry))->terminate(engine); + } + } +} -ShadowMapManager::ShadowTechnique ShadowMapManager::update(FEngine& engine, FView& view, +ShadowMapManager::ShadowTechnique ShadowMapManager::update( + Builder const& builder, + FEngine& engine, FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData, FScene::LightSoa const& lightData) noexcept { + + if (!builder.mDirectionalShadowMapCount && !builder.mSpotShadowMapCount) { + // no shadows were recorder + return ShadowTechnique::NONE; + } + + // initialize the shadowmap array the first time + if (UTILS_UNLIKELY(!mInitialized)) { + mInitialized = true; + // initialize our ShadowMap array in-place + mShadowUbh = engine.getDriverApi().createBufferObject(mShadowUb.getSize(), + BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC); + UTILS_NOUNROLL + for (auto& entry: mShadowMapCache) { + new(&entry) ShadowMap(engine); + } + } + + mDirectionalShadowMapCount = builder.mDirectionalShadowMapCount; + mSpotShadowMapCount = builder.mSpotShadowMapCount; + + for (auto const& entry : builder.mShadowMaps) { + auto& shadowMap = getShadowMap(entry.shadowIndex); + shadowMap.initialize( + entry.lightIndex, + entry.shadowType, + entry.shadowIndex, + entry.face, + entry.options); + } + ShadowTechnique shadowTechnique = {}; calculateTextureRequirements(engine, view, lightData); @@ -89,47 +160,54 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(FEngine& engine, FVie return shadowTechnique; } -void ShadowMapManager::reset() noexcept { - mCascadeShadowMaps.clear(); - mSpotShadowMaps.clear(); -} - -void ShadowMapManager::setDirectionalShadowMap(size_t lightIndex, +ShadowMapManager::Builder& ShadowMapManager::Builder::directionalShadowMap(size_t lightIndex, LightManager::ShadowOptions const* options) noexcept { assert_invariant(options->shadowCascades <= CONFIG_MAX_SHADOW_CASCADES); + // this updates getCascadedShadowMap() + mDirectionalShadowMapCount = options->shadowCascades; for (size_t c = 0; c < options->shadowCascades; c++) { - const size_t i = c; - assert_invariant(i < CONFIG_MAX_SHADOW_CASCADES); - auto* pShadowMap = getShadowMap(i); - pShadowMap->initialize(lightIndex, ShadowType::DIRECTIONAL, i, 0, options); - mCascadeShadowMaps.push_back(pShadowMap); + mShadowMaps.push_back({ + .lightIndex = lightIndex, + .shadowType = ShadowType::DIRECTIONAL, + .shadowIndex = uint8_t(c), + .face = 0, + .options = options }); } + return *this; } -void ShadowMapManager::addShadowMap(size_t lightIndex, bool spotlight, +ShadowMapManager::Builder& ShadowMapManager::Builder::shadowMap(size_t lightIndex, bool spotlight, LightManager::ShadowOptions const* options) noexcept { if (spotlight) { - const size_t c = mSpotShadowMaps.size(); + const size_t c = mSpotShadowMapCount++; const size_t i = c + CONFIG_MAX_SHADOW_CASCADES; assert_invariant(i < CONFIG_MAX_SHADOWMAPS); - auto* pShadowMap = getShadowMap(i); - pShadowMap->initialize(lightIndex, ShadowType::SPOT, i, 0, options); - mSpotShadowMaps.push_back(pShadowMap); + mShadowMaps.push_back({ + .lightIndex = lightIndex, + .shadowType = ShadowType::SPOT, + .shadowIndex = uint8_t(i), + .face = 0, + .options = options }); } else { // point-light, generate 6 independent shadowmaps for (size_t face = 0; face < 6; face++) { - const size_t c = mSpotShadowMaps.size(); + const size_t c = mSpotShadowMapCount++; const size_t i = c + CONFIG_MAX_SHADOW_CASCADES; assert_invariant(i < CONFIG_MAX_SHADOWMAPS); - auto* pShadowMap = getShadowMap(i); - pShadowMap->initialize(lightIndex, ShadowType::POINT, i, face, options); - mSpotShadowMaps.push_back(pShadowMap); + mShadowMaps.push_back({ + .lightIndex = lightIndex, + .shadowType = ShadowType::POINT, + .shadowIndex = uint8_t(i), + .face = uint8_t(face), + .options = options }); } } + return *this; } FrameGraphId ShadowMapManager::render(FEngine& engine, FrameGraph& fg, - RenderPass const& pass, FView& view, CameraInfo const& mainCameraInfo, + RenderPassBuilder const& passBuilder, + FView& view, CameraInfo const& mainCameraInfo, float4 const& userTime) noexcept { const float moment2 = std::numeric_limits::max(); @@ -179,11 +257,11 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // Directional, cascaded shadow maps auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters(); if (!directionalShadowCastersRange.empty()) { - for (auto* pShadowMap : mCascadeShadowMaps) { + for (auto& shadowMap : getCascadedShadowMap()) { // for the directional light, we already know if it has visible shadows. - if (pShadowMap->hasVisibleShadows()) { + if (shadowMap.hasVisibleShadows()) { passList.push_back({ - {}, pShadowMap, directionalShadowCastersRange, + {}, &shadowMap, directionalShadowCastersRange, VISIBLE_DIR_SHADOW_RENDERABLE }); } } @@ -192,11 +270,28 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // Point lights and Spotlight shadow maps auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters(); if (!spotShadowCastersRange.empty()) { - for (auto* pShadowMap : mSpotShadowMaps) { - assert_invariant(!pShadowMap->isDirectionalShadow()); - passList.push_back({ - {}, pShadowMap, spotShadowCastersRange, - VISIBLE_DYN_SHADOW_RENDERABLE }); + for (auto& shadowMap : getSpotShadowMaps()) { + assert_invariant(!shadowMap.isDirectionalShadow()); + + switch (shadowMap.getShadowType()) { + case ShadowType::DIRECTIONAL: + // we should never be here + break; + case ShadowType::SPOT: + prepareSpotShadowMap(shadowMap, engine, view, mainCameraInfo, + scene->getLightData(), mSceneInfo); + break; + case ShadowType::POINT: + preparePointShadowMap(shadowMap, engine, view, mainCameraInfo, + scene->getLightData()); + break; + } + + if (shadowMap.hasVisibleShadows()) { + passList.push_back({ + {}, &shadowMap, spotShadowCastersRange, + VISIBLE_DYN_SHADOW_RENDERABLE }); + } } } @@ -207,8 +302,9 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG builder.sideEffect(); }, [this, &engine, &view, vsmShadowOptions, - scene, mainCameraInfo, userTime, passTemplate = pass]( - FrameGraphResources const&, auto const& data, DriverApi& driver) { + scene, mainCameraInfo, userTime, passBuilder = passBuilder]( + FrameGraphResources const&, auto const& data, DriverApi& driver) mutable { + // Note: we could almost parallel_for the loop below, the problem currently is // that updatePrimitivesLod() updates temporary global state. @@ -218,69 +314,70 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // Generate a RenderPass for each shadow map for (auto const& entry : data.passList) { - ShadowMap& shadowMap = *entry.shadowMap; + ShadowMap const& shadowMap = *entry.shadowMap; + assert_invariant(shadowMap.hasVisibleShadows()); + + // Note: this loop can generate a lot of commands that come out of the + // "per frame command arena". The allocation persists until the + // end of the frame. + // One way to possibly mitigate this, would be to always use the + // same command buffer for all shadow map, but then we'd generate + // a lot of unneeded draw calls. + // To do this efficiently, we'd need a way to cull draw calls already + // recorded in the command buffer, per shadow map. + + // Note: the output of culling below is stored in scene->getRenderableData() - // for spot shadow map, we need to do the culling switch (shadowMap.getShadowType()) { case ShadowType::DIRECTIONAL: + // we should never be here break; case ShadowType::SPOT: - prepareSpotShadowMap(shadowMap, engine, view, mainCameraInfo, + ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view, scene->getRenderableData(), entry.range, - scene->getLightData(), mSceneInfo); + scene->getLightData()); break; case ShadowType::POINT: - preparePointShadowMap(shadowMap, engine, view, mainCameraInfo, + ShadowMapManager::cullPointShadowMap(shadowMap, view, scene->getRenderableData(), entry.range, - scene->getLightData(), mSceneInfo); + scene->getLightData()); break; } - if (shadowMap.hasVisibleShadows()) { - // Note: this loop can generate a lot of commands that come out of the - // "per frame command arena". The allocation persists until the - // end of the frame. - // One way to possibly mitigate this, would be to always use the - // same command buffer for all shadow map, but then we'd generate - // a lot of unneeded draw calls. - // To do this efficiently, we'd need a way to cull draw calls already - // recorded in the command buffer, per shadow map. - - - // cameraInfo only valid after calling update - const CameraInfo cameraInfo{ shadowMap.getCamera() }; - - auto transaction = ShadowMap::open(driver); - ShadowMap::prepareCamera(transaction, engine, cameraInfo); - ShadowMap::prepareViewport(transaction, shadowMap.getViewport()); - ShadowMap::prepareTime(transaction, engine, userTime); - ShadowMap::prepareShadowMapping(transaction, - vsmShadowOptions.highPrecision); - shadowMap.commit(transaction, driver); - - // updatePrimitivesLod must be run before RenderPass::appendCommands. - view.updatePrimitivesLod(engine, - cameraInfo, scene->getRenderableData(), entry.range); - - // generate and sort the commands for rendering the shadow map - RenderPass pass(passTemplate); - pass.setCamera(cameraInfo); - pass.setVisibilityMask(entry.visibilityMask); - pass.setGeometry(scene->getRenderableData(), - entry.range, scene->getRenderableUBO()); - pass.appendCommands(engine, RenderPass::SHADOW); - pass.sortCommands(engine); - - entry.executor = pass.getExecutor(); - - if (!view.hasVSM()) { - auto const* options = shadowMap.getShadowOptions(); - const PolygonOffset polygonOffset = { // handle reversed Z - .slope = -options->polygonOffsetSlope, - .constant = -options->polygonOffsetConstant - }; - entry.executor.overridePolygonOffset(&polygonOffset); - } + // cameraInfo only valid after calling update + const CameraInfo cameraInfo{ shadowMap.getCamera(), mainCameraInfo }; + + auto transaction = ShadowMap::open(driver); + ShadowMap::prepareCamera(transaction, engine, cameraInfo); + ShadowMap::prepareViewport(transaction, shadowMap.getViewport()); + ShadowMap::prepareTime(transaction, engine, userTime); + ShadowMap::prepareShadowMapping(transaction, + vsmShadowOptions.highPrecision); + shadowMap.commit(transaction, driver); + + // updatePrimitivesLod must be run before RenderPass::appendCommands. + view.updatePrimitivesLod(engine, + cameraInfo, scene->getRenderableData(), entry.range); + + // generate and sort the commands for rendering the shadow map + + RenderPass const pass = passBuilder + .camera(cameraInfo) + .visibilityMask(entry.visibilityMask) + .geometry(scene->getRenderableData(), + entry.range, scene->getRenderableUBO()) + .commandTypeFlags(RenderPass::CommandTypeFlags::SHADOW) + .build(engine); + + entry.executor = pass.getExecutor(); + + if (!view.hasVSM()) { + auto const* options = shadowMap.getShadowOptions(); + PolygonOffset const polygonOffset = { // handle reversed Z + .slope = -options->polygonOffsetSlope, + .constant = -options->polygonOffsetConstant + }; + entry.executor.overridePolygonOffset(&polygonOffset); } } @@ -295,6 +392,8 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // Shadow Passes // ------------------------------------------------------------------------------------------- + fg.getBlackboard()["shadowmap"] = prepareShadowPass->shadows; + struct ShadowPassData { FrameGraphId tempBlurSrc{}; // temporary shadowmap when blurring FrameGraphId output; @@ -303,10 +402,8 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG auto const& passList = prepareShadowPass.getData().passList; for (auto const& entry: passList) { - if (!entry.shadowMap->hasVisibleShadows()) { - continue; - } - + assert_invariant(entry.shadowMap->hasVisibleShadows()); + const uint8_t layer = entry.shadowMap->getLayer(); const auto* options = entry.shadowMap->getShadowOptions(); const auto msaaSamples = textureRequirements.msaaSamples; @@ -394,7 +491,6 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG auto rt = resources.getRenderPassInfo(data.rt); - engine.flush(); driver.beginRenderPass(rt.target, rt.params); entry.shadowMap->bind(driver); entry.executor.overrideScissor(entry.shadowMap->getScissor()); @@ -517,12 +613,13 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng }; bool hasVisibleShadows = false; - if (!mCascadeShadowMaps.empty()) { + utils::Slice cascadedShadowMaps = getCascadedShadowMap(); + if (!cascadedShadowMaps.empty()) { // Even if we have more than one cascade, we cull directional shadow casters against the // entire camera frustum, as if we only had a single cascade. - ShadowMap& shadowMap = *mCascadeShadowMaps[0]; + ShadowMap& shadowMap = cascadedShadowMaps[0]; - const auto direction = options.transform * lightData.elementAt(0); + const auto direction = lightData.elementAt(0); // We compute the directional light's model matrix using the origin's as the light position. // The choice of the light's origin initially doesn't matter for a directional light. @@ -533,7 +630,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng // Compute scene-dependent values shared across all cascades ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo); - shadowMap.updateDirectional(mEngine, + shadowMap.updateDirectional(engine, lightData, 0, cameraInfo, shadowMapInfo, sceneInfo); hasVisibleShadows = shadowMap.hasVisibleShadows(); @@ -558,7 +655,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng vsFar = std::max(vsFar, sceneInfo.vsNearFar.y); } - const size_t cascadeCount = mCascadeShadowMaps.size(); + const size_t cascadeCount = cascadedShadowMaps.size(); // We divide the camera frustum into N cascades. This gives us N + 1 split positions. // The first split position is the near plane; the last split position is the far plane. @@ -592,16 +689,14 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng // note: normalBias is set to zero for VSM const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0); - for (size_t i = 0, c = mCascadeShadowMaps.size(); i < c; i++) { - assert_invariant(mCascadeShadowMaps[i]); - + for (size_t i = 0, c = cascadedShadowMaps.size(); i < c; i++) { // Compute the frustum for the directional light. - ShadowMap& shadowMap = *mCascadeShadowMaps[i]; + ShadowMap& shadowMap = cascadedShadowMaps[i]; assert_invariant(shadowMap.getLightIndex() == 0); sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] }; - auto shaderParameters = shadowMap.updateDirectional(mEngine, + auto shaderParameters = shadowMap.updateDirectional(engine, lightData, 0, cameraInfo, shadowMapInfo, sceneInfo); if (shadowMap.hasVisibleShadows()) { @@ -643,7 +738,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng } uint32_t cascades = 0; - cascades |= uint32_t(mCascadeShadowMaps.size()); + cascades |= uint32_t(cascadedShadowMaps.size()); cascades |= cascadeHasVisibleShadows << 8u; mShadowMappingUniforms.directionalShadows = directionalShadowsMask; @@ -677,15 +772,58 @@ void ShadowMapManager::updateSpotVisibilityMasks( } } -void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, - FEngine& engine, FView& view, CameraInfo const& mainCameraInfo, - FScene::RenderableSoa& renderableData, utils::Range range, +void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engine, FView& view, + CameraInfo const& mainCameraInfo, FScene::LightSoa& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept { + + const size_t lightIndex = shadowMap.getLightIndex(); + FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions(); + + // update the shadow map frustum/camera + const ShadowMap::ShadowMapInfo shadowMapInfo{ + .atlasDimension = mTextureAtlasRequirements.size, + .textureDimension = uint16_t(options->mapSize), + .shadowDimension = uint16_t(options->mapSize - 2u), + .textureSpaceFlipped = engine.getBackend() == Backend::METAL || + engine.getBackend() == Backend::VULKAN, + .vsm = view.hasVSM() + }; + + auto shaderParameters = shadowMap.updateSpot(engine, + lightData, lightIndex, mainCameraInfo, shadowMapInfo, *view.getScene(), sceneInfo); + + // and if we need to generate it, update all the UBO data + if (shadowMap.hasVisibleShadows()) { + const size_t shadowIndex = shadowMap.getShadowIndex(); + const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs; + // note: normalBias is set to zero for VSM + const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias; + + auto& s = mShadowUb.edit(); + const double n = shadowMap.getCamera().getNear(); + const double f = shadowMap.getCamera().getCullingFar(); + s.shadows[shadowIndex].layer = shadowMap.getLayer(); + s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace; + s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized; + s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter; + s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ; + s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter; + s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n)); + s.shadows[shadowIndex].elvsm = options->vsm.elvsm; + s.shadows[shadowIndex].bulbRadiusLs = + mSoftShadowOptions.penumbraScale * options->shadowBulbRadius + / wsTexelSizeAtOneMeter; + + } +} + +void ShadowMapManager::cullSpotShadowMap(ShadowMap const& shadowMap, FEngine& engine, FView& view, + FScene::RenderableSoa& renderableData, utils::Range range, + FScene::LightSoa& lightData) noexcept { auto& lcm = engine.getLightManager(); const size_t lightIndex = shadowMap.getLightIndex(); const FLightManager::Instance li = lightData.elementAt(lightIndex); - FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions(); // compute the frustum for this light // for spotlights, we cull shadow casters first because we already know the frustum, @@ -722,19 +860,28 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, visibility + range.first, visibleArray + range.first, range.size()); +} + +void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap, + FEngine& engine, FView& view, CameraInfo const& mainCameraInfo, + FScene::LightSoa& lightData) noexcept { + + const uint8_t face = shadowMap.getFace(); + const size_t lightIndex = shadowMap.getLightIndex(); + FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions(); // update the shadow map frustum/camera const ShadowMap::ShadowMapInfo shadowMapInfo{ .atlasDimension = mTextureAtlasRequirements.size, .textureDimension = uint16_t(options->mapSize), - .shadowDimension = uint16_t(options->mapSize - 2u), + .shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border .textureSpaceFlipped = engine.getBackend() == Backend::METAL || engine.getBackend() == Backend::VULKAN, .vsm = view.hasVSM() }; - auto shaderParameters = shadowMap.updateSpot(mEngine, - lightData, lightIndex, mainCameraInfo, shadowMapInfo, *view.getScene(), sceneInfo); + auto shaderParameters = shadowMap.updatePoint(engine, lightData, lightIndex, + mainCameraInfo, shadowMapInfo, *view.getScene(), face); // and if we need to generate it, update all the UBO data if (shadowMap.hasVisibleShadows()) { @@ -757,19 +904,15 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, s.shadows[shadowIndex].bulbRadiusLs = mSoftShadowOptions.penumbraScale * options->shadowBulbRadius / wsTexelSizeAtOneMeter; - } } -void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap, - FEngine& engine, FView& view, CameraInfo const& mainCameraInfo, +void ShadowMapManager::cullPointShadowMap(ShadowMap const& shadowMap, FView& view, FScene::RenderableSoa& renderableData, utils::Range range, - FScene::LightSoa& lightData, - ShadowMap::SceneInfo const& sceneInfo) noexcept { + FScene::LightSoa& lightData) noexcept { const uint8_t face = shadowMap.getFace(); const size_t lightIndex = shadowMap.getLightIndex(); - FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions(); // compute the frustum for this light // for spotlights, we cull shadow casters first because we already know the frustum, @@ -803,65 +946,29 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap, visibility + range.first, visibleArray + range.first, range.size()); - - // update the shadow map frustum/camera - const ShadowMap::ShadowMapInfo shadowMapInfo{ - .atlasDimension = mTextureAtlasRequirements.size, - .textureDimension = uint16_t(options->mapSize), - .shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border - .textureSpaceFlipped = engine.getBackend() == Backend::METAL || - engine.getBackend() == Backend::VULKAN, - .vsm = view.hasVSM() - }; - - auto shaderParameters = shadowMap.updatePoint(mEngine, lightData, lightIndex, - mainCameraInfo, shadowMapInfo, *view.getScene(), face); - - - // and if we need to generate it, update all the UBO data - if (shadowMap.hasVisibleShadows()) { - const size_t shadowIndex = shadowMap.getShadowIndex(); - const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs; - // note: normalBias is set to zero for VSM - const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias; - - auto& s = mShadowUb.edit(); - const double n = shadowMap.getCamera().getNear(); - const double f = shadowMap.getCamera().getCullingFar(); - s.shadows[shadowIndex].layer = shadowMap.getLayer(); - s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace; - s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized; - s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter; - s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ; - s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter; - s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n)); - s.shadows[shadowIndex].elvsm = options->vsm.elvsm; - s.shadows[shadowIndex].bulbRadiusLs = - mSoftShadowOptions.penumbraScale * options->shadowBulbRadius - / wsTexelSizeAtOneMeter; - } } ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine& engine, FScene::LightSoa const& lightData) noexcept { // The const_cast here is a little ugly, but conceptually lightData should be const, - // it's just that we're using it to store some temporary data. with SoA we can't have + // it's just that we're using it to store some temporary data. With SoA we can't have // a `mutable` element, so that's a workaround. FScene::ShadowInfo* const shadowInfo = const_cast( lightData.data()); ShadowTechnique shadowTechnique{}; - if (!mSpotShadowMaps.empty()) { + utils::Slice const spotShadowMaps = getSpotShadowMaps(); + if (!spotShadowMaps.empty()) { shadowTechnique |= ShadowTechnique::SHADOW_MAP; - for (auto const* pShadowMap : mSpotShadowMaps) { - const size_t lightIndex = pShadowMap->getLightIndex(); - // gather the per-light (not per shadow map) information. For point lights we will + for (ShadowMap const& shadowMap : spotShadowMaps) { + const size_t lightIndex = shadowMap.getLightIndex(); + // Gather the per-light (not per shadow map) information. For point lights we will // "see" 6 shadowmaps (one per face), we must use the first face one, the shader // knows how to find the entry for other faces (they're guaranteed to be sequential). - if (pShadowMap->getFace() == 0) { + if (shadowMap.getFace() == 0) { shadowInfo[lightIndex].castsShadows = true; // FIXME: is that set correctly? - shadowInfo[lightIndex].index = pShadowMap->getShadowIndex(); + shadowInfo[lightIndex].index = shadowMap.getShadowIndex(); } } } @@ -891,18 +998,18 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view uint8_t layer = 0; uint32_t maxDimension = 0; bool elvsm = false; - for (auto* pShadowMap : mCascadeShadowMaps) { + for (ShadowMap& shadowMap : getCascadedShadowMap()) { // Shadow map size should be the same for all cascades. - auto const& options = pShadowMap->getShadowOptions(); + auto const& options = shadowMap.getShadowOptions(); maxDimension = std::max(maxDimension, options->mapSize); elvsm = elvsm || options->vsm.elvsm; - pShadowMap->setLayer(layer++); + shadowMap.setLayer(layer++); } - for (auto& pShadowMap : mSpotShadowMaps) { - auto const& options = pShadowMap->getShadowOptions(); + for (ShadowMap& shadowMap : getSpotShadowMaps()) { + auto const& options = shadowMap.getShadowOptions(); maxDimension = std::max(maxDimension, options->mapSize); elvsm = elvsm || options->vsm.elvsm; - pShadowMap->setLayer(layer++); + shadowMap.setLayer(layer++); } const uint8_t layersNeeded = layer; @@ -944,6 +1051,10 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view mipLevels = std::max(1, FTexture::maxLevelCount(maxDimension) - lowMipmapLevel); } + // publish the debugging data + engine.debug.shadowmap.display_shadow_texture_layer_count = layersNeeded; + engine.debug.shadowmap.display_shadow_texture_level_count = mipLevels; + mTextureAtlasRequirements = { (uint16_t)maxDimension, layersNeeded, diff --git a/filament/src/ShadowMapManager.h b/filament/src/ShadowMapManager.h index 712e230266e..38017f5c8b1 100644 --- a/filament/src/ShadowMapManager.h +++ b/filament/src/ShadowMapManager.h @@ -17,32 +17,52 @@ #ifndef TNT_FILAMENT_DETAILS_SHADOWMAPMANAGER_H #define TNT_FILAMENT_DETAILS_SHADOWMAPMANAGER_H -#include - +#include "Culler.h" #include "ShadowMap.h" #include "TypedUniformBuffer.h" +#include +#include + +#include +#include + +#include "components/RenderableManager.h" + #include "details/Engine.h" #include "details/Scene.h" -#include +#include "fg/FrameGraphId.h" +#include "fg/FrameGraphTexture.h" -#include #include #include -#include +#include +#include +#include +#include +#include -#include +#include +#include #include #include +#include +#include +#include + +#include +#include namespace filament { +class FCamera; class FView; class FrameGraph; class RenderPass; +class RenderPassBuilder; struct ShadowMappingUniforms { math::float4 cascadeSplits; @@ -54,7 +74,7 @@ struct ShadowMappingUniforms { class ShadowMapManager { public: - using ShadowMappingUniforms = ShadowMappingUniforms; + using ShadowMappingUniforms = filament::ShadowMappingUniforms; using ShadowType = ShadowMap::ShadowType; @@ -64,40 +84,50 @@ class ShadowMapManager { SCREEN_SPACE = 0x2u, }; + class Builder { + friend class ShadowMapManager; + uint32_t mDirectionalShadowMapCount = 0; + uint32_t mSpotShadowMapCount = 0; + struct ShadowMap { + size_t lightIndex; + ShadowType shadowType; + uint16_t shadowIndex; + uint8_t face; + LightManager::ShadowOptions const* options; + }; + std::vector mShadowMaps; + public: + Builder& directionalShadowMap(size_t lightIndex, + LightManager::ShadowOptions const* options) noexcept; - explicit ShadowMapManager(FEngine& engine); - ~ShadowMapManager(); + Builder& shadowMap(size_t lightIndex, bool spotlight, + LightManager::ShadowOptions const* options) noexcept; - void terminate(FEngine& engine); + bool hasShadowMaps() const noexcept { + return mDirectionalShadowMapCount || mSpotShadowMapCount; + } + }; - // Reset shadow map layout. - void reset() noexcept; + ~ShadowMapManager(); - void setDirectionalShadowMap(size_t lightIndex, - LightManager::ShadowOptions const* options) noexcept; + static void createIfNeeded(FEngine& engine, + std::unique_ptr& inOutShadowMapManager); - void addShadowMap(size_t lightIndex, bool spotlight, - LightManager::ShadowOptions const* options) noexcept; + static void terminate(FEngine& engine, + std::unique_ptr& shadowMapManager); // Updates all the shadow maps and performs culling. // Returns true if any of the shadow maps have visible shadows. - ShadowMapManager::ShadowTechnique update(FEngine& engine, FView& view, + ShadowMapManager::ShadowTechnique update(Builder const& builder, + FEngine& engine, FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData, FScene::LightSoa const& lightData) noexcept; // Renders all the shadow maps. - FrameGraphId render(FEngine& engine, FrameGraph& fg, RenderPass const& pass, + FrameGraphId render(FEngine& engine, FrameGraph& fg, + RenderPassBuilder const& passBuilder, FView& view, CameraInfo const& mainCameraInfo, math::float4 const& userTime) noexcept; - ShadowMap* getShadowMap(size_t index) noexcept { - assert_invariant(index < CONFIG_MAX_SHADOWMAPS); - return std::launder(reinterpret_cast(&mShadowMapCache[index])); - } - - ShadowMap const* getShadowMap(size_t index) const noexcept { - return const_cast(this)->getShadowMap(index); - } - // valid after calling update() above ShadowMappingUniforms getShadowMappingUniforms() const noexcept { return mShadowMappingUniforms; @@ -105,9 +135,19 @@ class ShadowMapManager { auto& getShadowUniformsHandle() const { return mShadowUbh; } - bool hasSpotShadows() const { return !mSpotShadowMaps.empty(); } + bool hasSpotShadows() const { return !mSpotShadowMapCount; } + + // for debugging only + FCamera const* getDirectionalShadowCamera() const noexcept { + if (!mInitialized) return nullptr; + return getShadowMap(0).getDebugCamera(); + } private: + explicit ShadowMapManager(FEngine& engine); + + void terminate(FEngine& engine); + ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine, FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData, FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept; @@ -120,14 +160,20 @@ class ShadowMapManager { void prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engine, FView& view, CameraInfo const& mainCameraInfo, - FScene::RenderableSoa& renderableData, utils::Range range, FScene::LightSoa& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept; + static void cullSpotShadowMap(ShadowMap const& map, + FEngine& engine, FView& view, + FScene::RenderableSoa& renderableData, utils::Range range, + FScene::LightSoa& lightData) noexcept; + void preparePointShadowMap(ShadowMap& map, FEngine& engine, FView& view, CameraInfo const& mainCameraInfo, + FScene::LightSoa& lightData) noexcept; + + static void cullPointShadowMap(ShadowMap const& shadowMap, FView& view, FScene::RenderableSoa& renderableData, utils::Range range, - FScene::LightSoa& lightData, - ShadowMap::SceneInfo const& sceneInfo) noexcept; + FScene::LightSoa& lightData) noexcept; static void updateSpotVisibilityMasks( uint8_t visibleLayers, @@ -163,8 +209,6 @@ class ShadowMapManager { size_t mSplitCount; }; - FEngine& mEngine; - // Atlas requirements, updated in ShadowMapManager::update(), // consumed in ShadowMapManager::render() struct TextureAtlasRequirements { @@ -184,19 +228,34 @@ class ShadowMapManager { ShadowMap::SceneInfo mSceneInfo; - utils::FixedCapacityVector mCascadeShadowMaps{ - utils::FixedCapacityVector::with_capacity( - CONFIG_MAX_SHADOW_CASCADES) }; - - utils::FixedCapacityVector mSpotShadowMaps{ - utils::FixedCapacityVector::with_capacity( - CONFIG_MAX_SHADOWMAPS - CONFIG_MAX_SHADOW_CASCADES) }; - // Inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly. // Because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations. // Each ShadowMap is currently 40 bytes (total of 2.5KB for 64 shadow maps) using ShadowMapStorage = std::aligned_storage::type; - std::array mShadowMapCache; + using ShadowMapCacheContainer = std::array; + ShadowMapCacheContainer mShadowMapCache; + uint32_t mDirectionalShadowMapCount = 0; + uint32_t mSpotShadowMapCount = 0; + bool mInitialized = false; + + ShadowMap& getShadowMap(size_t index) noexcept { + assert_invariant(index < CONFIG_MAX_SHADOWMAPS); + return *std::launder(reinterpret_cast(&mShadowMapCache[index])); + } + + ShadowMap const& getShadowMap(size_t index) const noexcept { + return const_cast(this)->getShadowMap(index); + } + + utils::Slice getCascadedShadowMap() noexcept { + ShadowMap* const p = &getShadowMap(0); + return { p, mDirectionalShadowMapCount }; + } + + utils::Slice getSpotShadowMaps() noexcept { + ShadowMap* const p = &getShadowMap(CONFIG_MAX_SHADOW_CASCADES); + return { p, mSpotShadowMapCount }; + } }; } // namespace filament diff --git a/filament/src/Stream.cpp b/filament/src/Stream.cpp index c895248e371..8bba0dd6b57 100644 --- a/filament/src/Stream.cpp +++ b/filament/src/Stream.cpp @@ -28,7 +28,8 @@ void Stream::setAcquiredImage(void* image, Callback callback, void* userdata) no downcast(this)->setAcquiredImage(image, callback, userdata); } -void Stream::setAcquiredImage(void* image, backend::CallbackHandler* handler, Callback callback, void* userdata) noexcept { +void Stream::setAcquiredImage(void* image, + backend::CallbackHandler* handler, Callback callback, void* userdata) noexcept { downcast(this)->setAcquiredImage(image, handler, callback, userdata); } diff --git a/filament/src/SwapChain.cpp b/filament/src/SwapChain.cpp index c30bce69416..a242ef06ccb 100644 --- a/filament/src/SwapChain.cpp +++ b/filament/src/SwapChain.cpp @@ -18,6 +18,10 @@ #include "details/Engine.h" +#include + +#include + namespace filament { void* SwapChain::getNativeWindow() const noexcept { @@ -25,7 +29,11 @@ void* SwapChain::getNativeWindow() const noexcept { } void SwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) { - return downcast(this)->setFrameScheduledCallback(callback, user); + downcast(this)->setFrameScheduledCallback(callback, user); +} + +SwapChain::FrameScheduledCallback SwapChain::getFrameScheduledCallback() const noexcept { + return downcast(this)->getFrameScheduledCallback(); } void SwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler, @@ -37,4 +45,8 @@ bool SwapChain::isSRGBSwapChainSupported(Engine& engine) noexcept { return FSwapChain::isSRGBSwapChainSupported(downcast(engine)); } +bool SwapChain::isProtectedContentSupported(Engine& engine) noexcept { + return FSwapChain::isProtectedContentSupported(downcast(engine)); +} + } // namespace filament diff --git a/filament/src/Texture.cpp b/filament/src/Texture.cpp index 3ed5687b47b..f4c381927a7 100644 --- a/filament/src/Texture.cpp +++ b/filament/src/Texture.cpp @@ -78,6 +78,10 @@ bool Texture::isTextureFormatSupported(Engine& engine, InternalFormat format) no return FTexture::isTextureFormatSupported(downcast(engine), format); } +bool Texture::isProtectedTexturesSupported(Engine& engine) noexcept { + return FTexture::isProtectedTexturesSupported(downcast(engine)); +} + bool Texture::isTextureSwizzleSupported(Engine& engine) noexcept { return FTexture::isTextureSwizzleSupported(downcast(engine)); } diff --git a/filament/src/ToneMapper.cpp b/filament/src/ToneMapper.cpp index b24f253e0a1..9c5a191210c 100644 --- a/filament/src/ToneMapper.cpp +++ b/filament/src/ToneMapper.cpp @@ -230,6 +230,32 @@ float3 FilmicToneMapper::operator()(math::float3 x) const noexcept { return (x * (a * x + b)) / (x * (c * x + d) + e); } +//------------------------------------------------------------------------------ +// PBR Neutral tone mapper +//------------------------------------------------------------------------------ + +DEFAULT_CONSTRUCTORS(PBRNeutralToneMapper) + +float3 PBRNeutralToneMapper::operator()(math::float3 color) const noexcept { + // PBR Tone Mapping, https://modelviewer.dev/examples/tone-mapping.html + constexpr float startCompression = 0.8f - 0.04f; + constexpr float desaturation = 0.15f; + + float x = min(color.r, min(color.g, color.b)); + float offset = x < 0.08f ? x - 6.25f * x * x : 0.04f; + color -= offset; + + float peak = max(color.r, max(color.g, color.b)); + if (peak < startCompression) return color; + + float d = 1.0f - startCompression; + float newPeak = 1.0f - d * d / (peak + d - startCompression); + color *= newPeak / peak; + + float g = 1.0f - 1.0f / (desaturation * (peak - newPeak) + 1.0f); + return mix(color, float3(newPeak), g); +} + //------------------------------------------------------------------------------ // AgX tone mapper //------------------------------------------------------------------------------ @@ -262,14 +288,14 @@ float3 agxDefaultContrastApprox(float3 x) { float3 x2 = x * x; float3 x4 = x2 * x2; float3 x6 = x4 * x2; - return - 17.86 * x6 * x - + 78.01 * x6 - - 126.7 * x4 * x - + 92.06 * x4 - - 28.72 * x2 * x - + 4.361 * x2 - - 0.1718 * x - + 0.002857; + return - 17.86f * x6 * x + + 78.01f * x6 + - 126.7f * x4 * x + + 92.06f * x4 + - 28.72f * x2 * x + + 4.361f * x2 + - 0.1718f * x + + 0.002857f; } // Adapted from https://iolite-engine.com/blog_posts/minimal_agx_implementation @@ -278,23 +304,23 @@ float3 agxLook(float3 val, AgxToneMapper::AgxLook look) { return val; } - const float3 lw = float3(0.2126, 0.7152, 0.0722); + const float3 lw = float3(0.2126f, 0.7152f, 0.0722f); float luma = dot(val, lw); // Default - float3 offset = float3(0.0); - float3 slope = float3(1.0); - float3 power = float3(1.0); - float sat = 1.0; + float3 offset = float3(0.0f); + float3 slope = float3(1.0f); + float3 power = float3(1.0f); + float sat = 1.0f; if (look == AgxToneMapper::AgxLook::GOLDEN) { - slope = float3(1.0, 0.9, 0.5); - power = float3(0.8); + slope = float3(1.0f, 0.9f, 0.5f); + power = float3(0.8f); sat = 1.3; } if (look == AgxToneMapper::AgxLook::PUNCHY) { - slope = float3(1.0); - power = float3(1.35, 1.35, 1.35); + slope = float3(1.0f); + power = float3(1.35f, 1.35f, 1.35f); sat = 1.4; } @@ -305,16 +331,16 @@ float3 agxLook(float3 val, AgxToneMapper::AgxLook look) { float3 AgxToneMapper::operator()(float3 v) const noexcept { // Ensure no negative values - v = max(float3(0.0), v); + v = max(float3(0.0f), v); v = AgXInsetMatrix * v; // Log2 encoding - v = max(v, 1E-10); // avoid 0 or negative numbers for log2 + v = max(v, 1E-10f); // avoid 0 or negative numbers for log2 v = log2(v); v = (v - AgxMinEv) / (AgxMaxEv - AgxMinEv); - v = clamp(v, 0, 1); + v = clamp(v, 0.0f, 1.0f); // Apply sigmoid v = agxDefaultContrastApprox(v); @@ -325,7 +351,7 @@ float3 AgxToneMapper::operator()(float3 v) const noexcept { v = AgXOutsetMatrix * v; // Linearize - v = pow(max(float3(0.0), v), 2.2); + v = pow(max(float3(0.0f), v), 2.2f); return v; } diff --git a/filament/src/TransformManager.cpp b/filament/src/TransformManager.cpp index 4a6b672fe75..384b930c506 100644 --- a/filament/src/TransformManager.cpp +++ b/filament/src/TransformManager.cpp @@ -22,6 +22,30 @@ namespace filament { using namespace math; +bool TransformManager::hasComponent(Entity e) const noexcept { + return downcast(this)->hasComponent(e); +} + +size_t TransformManager::getComponentCount() const noexcept { + return downcast(this)->getComponentCount(); +} + +bool TransformManager::empty() const noexcept { + return downcast(this)->empty(); +} + +utils::Entity TransformManager::getEntity(TransformManager::Instance i) const noexcept { + return downcast(this)->getEntity(i); +} + +utils::Entity const* TransformManager::getEntities() const noexcept { + return downcast(this)->getEntities(); +} + +TransformManager::Instance TransformManager::getInstance(Entity e) const noexcept { + return downcast(this)->getInstance(e); +} + void TransformManager::create(Entity entity, Instance parent, const mat4f& worldTransform) { downcast(this)->create(entity, parent, worldTransform); } @@ -38,14 +62,6 @@ void TransformManager::destroy(Entity e) noexcept { downcast(this)->destroy(e); } -bool TransformManager::hasComponent(Entity e) const noexcept { - return downcast(this)->hasComponent(e); -} - -TransformManager::Instance TransformManager::getInstance(Entity e) const noexcept { - return downcast(this)->getInstance(e); -} - void TransformManager::setTransform(Instance ci, const mat4f& model) noexcept { downcast(this)->setTransform(ci, model); } @@ -58,7 +74,7 @@ const mat4f& TransformManager::getTransform(Instance ci) const noexcept { return downcast(this)->getTransform(ci); } -const mat4 TransformManager::getTransformAccurate(Instance ci) const noexcept { +mat4 TransformManager::getTransformAccurate(Instance ci) const noexcept { return downcast(this)->getTransformAccurate(ci); } @@ -66,7 +82,7 @@ const mat4f& TransformManager::getWorldTransform(Instance ci) const noexcept { return downcast(this)->getWorldTransform(ci); } -const mat4 TransformManager::getWorldTransformAccurate(Instance ci) const noexcept { +mat4 TransformManager::getWorldTransformAccurate(Instance ci) const noexcept { return downcast(this)->getWorldTransformAccurate(ci); } @@ -110,7 +126,7 @@ void TransformManager::setAccurateTranslationsEnabled(bool enable) noexcept { } bool TransformManager::isAccurateTranslationsEnabled() const noexcept { - return downcast(this)->isAccurateTranslationsEnabled();; + return downcast(this)->isAccurateTranslationsEnabled(); } } // namespace filament diff --git a/filament/src/View.cpp b/filament/src/View.cpp index dd8e9380a75..2de966ea0c9 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -67,8 +67,8 @@ const char* View::getName() const noexcept { return downcast(this)->getName(); } -Camera const* View::getDirectionalLightCamera() const noexcept { - return downcast(this)->getDirectionalLightCamera(); +Camera const* View::getDirectionalShadowCamera() const noexcept { + return downcast(this)->getDirectionalShadowCamera(); } void View::setShadowingEnabled(bool enabled) noexcept { @@ -283,7 +283,7 @@ bool View::isStencilBufferEnabled() const noexcept { return downcast(this)->isStencilBufferEnabled(); } -void View::setStereoscopicOptions(const StereoscopicOptions& options) { +void View::setStereoscopicOptions(const StereoscopicOptions& options) noexcept { return downcast(this)->setStereoscopicOptions(options); } diff --git a/filament/src/components/CameraManager.cpp b/filament/src/components/CameraManager.cpp index 21dd9e81d0c..b48742eae6c 100644 --- a/filament/src/components/CameraManager.cpp +++ b/filament/src/components/CameraManager.cpp @@ -23,70 +23,82 @@ #include #include -#include - using namespace utils; using namespace filament::math; namespace filament { -FCameraManager::FCameraManager(FEngine& engine) noexcept - : mEngine(engine) { +FCameraManager::FCameraManager(FEngine&) noexcept { } -FCameraManager::~FCameraManager() noexcept { -} +FCameraManager::~FCameraManager() noexcept = default; -void FCameraManager::terminate() noexcept { +void FCameraManager::terminate(FEngine& engine) noexcept { auto& manager = mManager; if (!manager.empty()) { #ifndef NDEBUG slog.d << "cleaning up " << manager.getComponentCount() << " leaked Camera components" << io::endl; #endif - while (!manager.empty()) { - Instance const ci = manager.end() - 1; - destroy(manager.getEntity(ci)); + utils::Slice const entities{ manager.getEntities(), manager.getComponentCount() }; + for (Entity const e : entities) { + destroy(engine, e); } } } -void FCameraManager::gc(utils::EntityManager& em) noexcept { +void FCameraManager::gc(FEngine& engine, utils::EntityManager& em) noexcept { auto& manager = mManager; - manager.gc(em, 4, [this](Entity e) { - destroy(e); + manager.gc(em, [this, &engine](Entity e) { + destroy(engine, e); }); } -FCamera* FCameraManager::create(Entity entity) { - FEngine& engine = mEngine; +FCamera* FCameraManager::create(FEngine& engine, Entity entity) { auto& manager = mManager; + // if this entity already has Camera component, destroy it. if (UTILS_UNLIKELY(manager.hasComponent(entity))) { - destroy(entity); + destroy(engine, entity); } + + // add the Camera component to the entity Instance const i = manager.addComponent(entity); - FCamera* camera = engine.getHeapAllocator().make(engine, entity); + // For historical reasons, FCamera must not move. So the CameraManager stores a pointer. + FCamera* const camera = engine.getHeapAllocator().make(engine, entity); manager.elementAt(i) = camera; + manager.elementAt(i) = false; // Make sure we have a transform component - FTransformManager& transformManager = engine.getTransformManager(); - if (!transformManager.hasComponent(entity)) { - transformManager.create(entity); + FTransformManager& tcm = engine.getTransformManager(); + if (!tcm.hasComponent(entity)) { + tcm.create(entity); + manager.elementAt(i) = true; } return camera; } -void FCameraManager::destroy(Entity e) noexcept { +void FCameraManager::destroy(FEngine& engine, Entity e) noexcept { auto& manager = mManager; - Instance const i = manager.getInstance(e); - if (i) { - FCamera* camera = manager.elementAt(i); - assert_invariant(camera); - camera->terminate(mEngine); - mEngine.getHeapAllocator().destroy(camera); - manager.removeComponent(e); + if (Instance const i = manager.getInstance(e) ; i) { + // destroy the FCamera object + bool const ownsTransformComponent = manager.elementAt(i); + + { // scope for camera -- it's invalid after this scope. + FCamera* const camera = manager.elementAt(i); + assert_invariant(camera); + camera->terminate(engine); + engine.getHeapAllocator().destroy(camera); + + // Remove the camera component + manager.removeComponent(e); + } + + // if we added the transform component, remove it. + if (ownsTransformComponent) { + engine.getTransformManager().destroy(e); + } } } diff --git a/filament/src/components/CameraManager.h b/filament/src/components/CameraManager.h index c6753f95539..67bbfcb9aed 100644 --- a/filament/src/components/CameraManager.h +++ b/filament/src/components/CameraManager.h @@ -44,9 +44,9 @@ class UTILS_PRIVATE FCameraManager : public CameraManager { ~FCameraManager() noexcept; // free-up all resources - void terminate() noexcept; + void terminate(FEngine& engine) noexcept; - void gc(utils::EntityManager& em) noexcept; + void gc(FEngine& engine, utils::EntityManager& em) noexcept; /* * Component Manager APIs @@ -57,32 +57,47 @@ class UTILS_PRIVATE FCameraManager : public CameraManager { } Instance getInstance(utils::Entity e) const noexcept { - return Instance(mManager.getInstance(e)); + return { mManager.getInstance(e) }; + } + + size_t getComponentCount() const noexcept { + return mManager.getComponentCount(); + } + + bool empty() const noexcept { + return mManager.empty(); + } + + utils::Entity getEntity(Instance i) const noexcept { + return mManager.getEntity(i); + } + + utils::Entity const* getEntities() const noexcept { + return mManager.getEntities(); } FCamera* getCamera(Instance i) noexcept { return mManager.elementAt(i); } - FCamera* create(utils::Entity entity); + FCamera* create(FEngine& engine, utils::Entity entity); - void destroy(utils::Entity e) noexcept; + void destroy(FEngine& engine, utils::Entity e) noexcept; private: enum { - CAMERA + CAMERA, + OWNS_TRANSFORM_COMPONENT }; - using Base = utils::SingleInstanceComponentManager; + using Base = utils::SingleInstanceComponentManager; struct CameraManagerImpl : public Base { using Base::gc; using Base::swap; using Base::hasComponent; } mManager; - - FEngine& mEngine; }; } // namespace filament diff --git a/filament/src/components/LightManager.cpp b/filament/src/components/LightManager.cpp index 76e2beea67c..6393caa10aa 100644 --- a/filament/src/components/LightManager.cpp +++ b/filament/src/components/LightManager.cpp @@ -227,7 +227,9 @@ void FLightManager::terminate() noexcept { } } void FLightManager::gc(utils::EntityManager& em) noexcept { - mManager.gc(em); + mManager.gc(em, [this](Entity e) { + destroy(e); + }); } void FLightManager::setShadowOptions(Instance i, ShadowOptions const& options) noexcept { diff --git a/filament/src/components/LightManager.h b/filament/src/components/LightManager.h index 47f5cfcc275..317ec723f72 100644 --- a/filament/src/components/LightManager.h +++ b/filament/src/components/LightManager.h @@ -46,20 +46,32 @@ class FLightManager : public LightManager { void gc(utils::EntityManager& em) noexcept; + /* + * Component Manager APIs + */ + + bool hasComponent(utils::Entity e) const noexcept { + return mManager.hasComponent(e); + } + + Instance getInstance(utils::Entity e) const noexcept { + return { mManager.getInstance(e) }; + } + size_t getComponentCount() const noexcept { return mManager.getComponentCount(); } - utils::Entity const* getEntities() const noexcept { - return mManager.getEntities(); + bool empty() const noexcept { + return mManager.empty(); } - bool hasComponent(utils::Entity e) const noexcept { - return mManager.hasComponent(e); + utils::Entity getEntity(Instance i) const noexcept { + return mManager.getEntity(i); } - Instance getInstance(utils::Entity e) const noexcept { - return mManager.getInstance(e); + utils::Entity const* getEntities() const noexcept { + return mManager.getEntities(); } void create(const FLightManager::Builder& builder, utils::Entity entity); diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index d43fdb59d58..3bbd85597ce 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -58,6 +58,7 @@ struct RenderableManager::BuilderDetails { bool mScreenSpaceContactShadows : 1; bool mSkinningBufferMode : 1; bool mFogEnabled : 1; + RenderableManager::Builder::GeometryType mGeometryType : 2; size_t mSkinningBoneCount = 0; size_t mMorphTargetCount = 0; Bone const* mUserBones = nullptr; @@ -75,7 +76,9 @@ struct RenderableManager::BuilderDetails { explicit BuilderDetails(size_t count) : mEntries(count), mCulling(true), mCastShadows(false), mReceiveShadows(true), mScreenSpaceContactShadows(false), - mSkinningBufferMode(false), mFogEnabled(true), mBonePairs() { + mSkinningBufferMode(false), mFogEnabled(true), + mGeometryType(RenderableManager::Builder::GeometryType::DYNAMIC), + mBonePairs() { } // this is only needed for the explicit instantiation below BuilderDetails() = default; @@ -109,20 +112,23 @@ RenderableManager::Builder& RenderableManager::Builder::geometry(size_t index, RenderableManager::Builder& RenderableManager::Builder::geometry(size_t index, PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices, - size_t offset, size_t minIndex, size_t maxIndex, size_t count) noexcept { + size_t offset, UTILS_UNUSED size_t minIndex, UTILS_UNUSED size_t maxIndex, size_t count) noexcept { std::vector& entries = mImpl->mEntries; if (index < entries.size()) { entries[index].vertices = vertices; entries[index].indices = indices; entries[index].offset = offset; - entries[index].minIndex = minIndex; - entries[index].maxIndex = maxIndex; entries[index].count = count; entries[index].type = type; } return *this; } +RenderableManager::Builder& RenderableManager::Builder::geometryType(GeometryType type) noexcept { + mImpl->mGeometryType = type; + return *this; +} + RenderableManager::Builder& RenderableManager::Builder::material(size_t index, MaterialInstance const* materialInstance) noexcept { if (index < mImpl->mEntries.size()) { @@ -214,12 +220,12 @@ RenderableManager::Builder& RenderableManager::Builder::enableSkinningBuffers(bo RenderableManager::Builder& RenderableManager::Builder::boneIndicesAndWeights(size_t primitiveIndex, math::float2 const* indicesAndWeights, size_t count, size_t bonesPerVertex) noexcept { - size_t vertexCount = count / bonesPerVertex; + size_t const vertexCount = count / bonesPerVertex; utils::FixedCapacityVector> bonePairs(vertexCount); - for ( size_t iVertex = 0; iVertex < vertexCount; iVertex++) { + for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) { utils::FixedCapacityVector vertexData(bonesPerVertex); std::copy_n(indicesAndWeights + iVertex * bonesPerVertex, - bonesPerVertex, vertexData.data()); + bonesPerVertex, vertexData.data()); bonePairs[iVertex] = std::move(vertexData); } return boneIndicesAndWeights(primitiveIndex, bonePairs); @@ -275,13 +281,13 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi size_t maxPairsCount = 0; //size of texture, number of bone pairs size_t maxPairsCountPerVertex = 0; //maximum of number of bone per vertex - for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair){ - auto primitiveIndex = iBonePair->first; + for (auto& bonePair: mBonePairs) { + auto primitiveIndex = bonePair.first; auto entries = mEntries; ASSERT_PRECONDITION(primitiveIndex < entries.size() && primitiveIndex >= 0, "[primitive @ %u] primitiveindex is out of size (%u)", primitiveIndex, entries.size()); auto entry = mEntries[primitiveIndex]; - auto bonePairsForPrimitive = iBonePair->second; + auto bonePairsForPrimitive = bonePair.second; auto vertexCount = entry.vertices->getVertexCount(); ASSERT_PRECONDITION(bonePairsForPrimitive.size() == vertexCount, "[primitive @ %u] bone indices and weights pairs count (%u) must be equal to vertex count (%u)", @@ -292,7 +298,7 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi "[entity=%u, primitive @ %u] for advanced skinning set VertexBuffer::Builder::advancedSkinning()", entity.getId(), primitiveIndex); for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) { - size_t bonesPerVertex = bonePairsForPrimitive[iVertex].size(); + size_t const bonesPerVertex = bonePairsForPrimitive[iVertex].size(); maxPairsCount += bonesPerVertex; maxPairsCountPerVertex = std::max(bonesPerVertex, maxPairsCountPerVertex); } @@ -303,29 +309,28 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi // final texture data, indices and weights mBoneIndicesAndWeights = utils::FixedCapacityVector(maxPairsCount); // temporary indices and weights for one vertex - std::unique_ptr tempPairs = std::make_unique - (maxPairsCountPerVertex); - for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair) { - auto primitiveIndex = iBonePair->first; - auto bonePairsForPrimitive = iBonePair->second; - if (!bonePairsForPrimitive.size()) { - continue; + auto const tempPairs = std::make_unique(maxPairsCountPerVertex); + for (auto& bonePair: mBonePairs) { + auto primitiveIndex = bonePair.first; + auto bonePairsForPrimitive = bonePair.second; + if (bonePairsForPrimitive.empty()) { + continue; } - size_t vertexCount = mEntries[primitiveIndex].vertices->getVertexCount(); - std::unique_ptr skinJoints = std::make_unique - (4 * vertexCount); // temporary indices for one vertex - std::unique_ptr skinWeights = std::make_unique - (4 * vertexCount); // temporary weights for one vertex + size_t const vertexCount = mEntries[primitiveIndex].vertices->getVertexCount(); + // temporary indices for one vertex + auto skinJoints = std::make_unique(4 * vertexCount); + // temporary weights for one vertex + auto skinWeights = std::make_unique(4 * vertexCount); for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) { size_t tempPairCount = 0; - float boneWeightsSum = 0; + double boneWeightsSum = 0; for (size_t k = 0; k < bonePairsForPrimitive[iVertex].size(); k++) { auto boneWeight = bonePairsForPrimitive[iVertex][k][1]; auto boneIndex = bonePairsForPrimitive[iVertex][k][0]; ASSERT_PRECONDITION(boneWeight >= 0, "[entity=%u, primitive @ %u] bone weight (%f) of vertex=%u is negative ", entity.getId(), primitiveIndex, boneWeight, iVertex); - if (boneWeight) { + if (boneWeight > 0.0f) { ASSERT_PRECONDITION(boneIndex >= 0, "[entity=%u, primitive @ %u] bone index (%i) of vertex=%u is negative ", entity.getId(), primitiveIndex, (int) boneIndex, iVertex); @@ -342,26 +347,37 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi ASSERT_PRECONDITION(boneWeightsSum > 0, "[entity=%u, primitive @ %u] sum of bone weights of vertex=%u is %f, it should be positive.", entity.getId(), primitiveIndex, iVertex, boneWeightsSum); - if (abs(boneWeightsSum - 1.f) > std::numeric_limits::epsilon()) { + + // see https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes + double const epsilon = 2e-7 * double(tempPairCount); + if (abs(boneWeightsSum - 1.0) <= epsilon) { + boneWeightsSum = 1.0; + } +#ifndef NDEBUG + else { utils::slog.w << "Warning of skinning: [entity=%" << entity.getId() << ", primitive @ %" << primitiveIndex << "] sum of bone weights of vertex=" << iVertex << " is " << boneWeightsSum << ", it should be one. Weights will be normalized." << utils::io::endl; } +#endif + // prepare data for vertex attributes auto offset = iVertex * 4; // set attributes, indices and weights, for <= 4 pairs - for (size_t j = 0, c = std::min((int) tempPairCount, 4); j < c; j++) { - skinJoints[j + offset] = tempPairs[j][0]; - skinWeights[j + offset] = tempPairs[j][1] / boneWeightsSum; + for (size_t j = 0, c = std::min((int)tempPairCount, 4); j < c; j++) { + skinJoints[j + offset] = uint16_t(tempPairs[j][0]); + skinWeights[j + offset] = tempPairs[j][1] / float(boneWeightsSum); } // prepare data for texture if (tempPairCount > 4) { // set attributes, indices and weights, for > 4 pairs - skinWeights[3 + offset] = -(float) (pairsCount + 1); // negative offset to texture 0..-1, 1..-2 - skinJoints[3 + offset] = (uint16_t) tempPairCount; // number pairs per vertex in texture + // number pairs per vertex in texture + skinJoints[3 + offset] = (uint16_t)tempPairCount; + // negative offset to texture 0..-1, 1..-2 + skinWeights[3 + offset] = -float(pairsCount + 1); for (size_t j = 3; j < tempPairCount; j++) { mBoneIndicesAndWeights[pairsCount][0] = tempPairs[j][0]; - mBoneIndicesAndWeights[pairsCount][1] = tempPairs[j][1] / boneWeightsSum; + mBoneIndicesAndWeights[pairsCount][1] = tempPairs[j][1] / float(boneWeightsSum); pairsCount++; } } @@ -380,13 +396,23 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng ASSERT_PRECONDITION(mImpl->mSkinningBoneCount <= CONFIG_MAX_BONE_COUNT, "bone count > %u", CONFIG_MAX_BONE_COUNT); + ASSERT_PRECONDITION(mImpl->mInstanceCount <= CONFIG_MAX_INSTANCES || !mImpl->mInstanceBuffer, "instance count is %zu, but instance count is limited to CONFIG_MAX_INSTANCES (%zu) " "instances when supplying transforms via an InstanceBuffer.", mImpl->mInstanceCount, CONFIG_MAX_INSTANCES); + + if (mImpl->mGeometryType == GeometryType::STATIC) { + ASSERT_PRECONDITION(mImpl->mSkinningBoneCount > 0, + "Skinning can't be used with STATIC geometry"); + + ASSERT_PRECONDITION(mImpl->mMorphTargetCount > 0, + "Morphing can't be used with STATIC geometry"); + } + if (mImpl->mInstanceBuffer) { - size_t bufferInstanceCount = mImpl->mInstanceBuffer->mInstanceCount; + size_t const bufferInstanceCount = mImpl->mInstanceBuffer->mInstanceCount; ASSERT_PRECONDITION(mImpl->mInstanceCount <= bufferInstanceCount, "instance count (%zu) must be less than or equal to the InstanceBuffer's instance " "count " @@ -402,7 +428,7 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng auto& entry = mImpl->mEntries[i]; // entry.materialInstance must be set to something even if indices/vertices are null - FMaterial const* material = nullptr; + FMaterial const* material; if (!entry.materialInstance) { material = downcast(engine.getDefaultMaterial()); entry.materialInstance = material->getDefaultInstance(); @@ -426,11 +452,6 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng entity.getId(), i, entry.offset, entry.count, entry.indices->getIndexCount()); - ASSERT_PRECONDITION(entry.minIndex <= entry.maxIndex, - "[entity=%u, primitive @ %u] minIndex (%u) > maxIndex (%u)", - entity.getId(), i, - entry.minIndex, entry.maxIndex); - // this can't be an error because (1) those values are not immutable, so the caller // could fix later, and (2) the material's shader will work (i.e. compile), and // use the default values for this attribute, which maybe be acceptable. @@ -516,6 +537,8 @@ void FRenderableManager::create( setSkinning(ci, false); setMorphing(ci, builder->mMorphTargetCount); setFogEnabled(ci, builder->mFogEnabled); + // do this after calling setAxisAlignedBoundingBox + static_cast(mManager[ci].visibility).geometryType = builder->mGeometryType; mManager[ci].channels = builder->mLightChannels; InstancesInfo& instances = manager[ci].instances; @@ -678,7 +701,9 @@ void FRenderableManager::terminate() noexcept { } void FRenderableManager::gc(utils::EntityManager& em) noexcept { - mManager.gc(em); + mManager.gc(em, [this](Entity e) { + destroy(e); + }); } // This is basically a Renderable's destructor. @@ -804,7 +829,7 @@ void FRenderableManager::setGeometryAt(Instance instance, uint8_t level, size_t Slice& primitives = getRenderPrimitives(instance, level); if (primitiveIndex < primitives.size()) { primitives[primitiveIndex].set(mHwRenderPrimitiveFactory, mEngine.getDriverApi(), - type, vertices, indices, offset, 0, vertices->getVertexCount() - 1, count); + type, vertices, indices, offset, count); } } } diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index d79d2ca3eeb..003026baa0d 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -22,22 +22,30 @@ #include "HwRenderPrimitiveFactory.h" #include "UniformBuffer.h" -#include "backend/DriverApiForward.h" - -#include +#include
    #include #include -#include
    - #include +#include +#include + +#include #include +#include #include #include #include +#include + +#include + +#include +#include + namespace filament { class FBufferObject; @@ -49,9 +57,12 @@ class FSkinningBuffer; class FVertexBuffer; class FTexture; +class MorphTargetBuffer; + class FRenderableManager : public RenderableManager { public: using Instance = RenderableManager::Instance; + using GeometryType = RenderableManager::Builder::GeometryType; // TODO: consider renaming, this pertains to material variants, not strictly visibility. struct Visibility { @@ -60,11 +71,13 @@ class FRenderableManager : public RenderableManager { bool castShadows : 1; bool receiveShadows : 1; bool culling : 1; + bool skinning : 1; bool morphing : 1; bool screenSpaceContactShadows : 1; bool reversedWindingOrder : 1; bool fog : 1; + GeometryType geometryType : 2; }; static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits"); @@ -92,14 +105,30 @@ class FRenderableManager : public RenderableManager { } Instance getInstance(utils::Entity e) const noexcept { - return mManager.getInstance(e); + return { mManager.getInstance(e) }; + } + + size_t getComponentCount() const noexcept { + return mManager.getComponentCount(); + } + + bool empty() const noexcept { + return mManager.empty(); + } + + utils::Entity getEntity(Instance i) const noexcept { + return mManager.getEntity(i); + } + + utils::Entity const* getEntities() const noexcept { + return mManager.getEntities(); } void create(const RenderableManager::Builder& builder, utils::Entity entity); void destroy(utils::Entity e) noexcept; - inline void setAxisAlignedBoundingBox(Instance instance, const Box& aabb) noexcept; + inline void setAxisAlignedBoundingBox(Instance instance, const Box& aabb); inline void setLayerMask(Instance instance, uint8_t select, uint8_t values) noexcept; @@ -120,13 +149,13 @@ class FRenderableManager : public RenderableManager { inline void setPrimitives(Instance instance, utils::Slice const& primitives) noexcept; - inline void setSkinning(Instance instance, bool enable) noexcept; + inline void setSkinning(Instance instance, bool enable); void setBones(Instance instance, Bone const* transforms, size_t boneCount, size_t offset = 0); void setBones(Instance instance, math::mat4f const* transforms, size_t boneCount, size_t offset = 0); void setSkinningBuffer(Instance instance, FSkinningBuffer* skinningBuffer, size_t count, size_t offset); - inline void setMorphing(Instance instance, bool enable) noexcept; + inline void setMorphing(Instance instance, bool enable); void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset); void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count); @@ -176,10 +205,6 @@ class FRenderableManager : public RenderableManager { static_assert(sizeof(InstancesInfo) == 16); inline InstancesInfo getInstancesInfo(Instance instance) const noexcept; - utils::Entity getEntity(Instance instance) const noexcept { - return mManager.getEntity(instance); - } - inline size_t getLevelCount(Instance) const noexcept { return 1u; } size_t getPrimitiveCount(Instance instance, uint8_t level) const noexcept; void setMaterialInstanceAt(Instance instance, uint8_t level, @@ -283,8 +308,12 @@ class FRenderableManager : public RenderableManager { FILAMENT_DOWNCAST(RenderableManager) -void FRenderableManager::setAxisAlignedBoundingBox(Instance instance, const Box& aabb) noexcept { +void FRenderableManager::setAxisAlignedBoundingBox(Instance instance, const Box& aabb) { if (instance) { + ASSERT_PRECONDITION( + static_cast( + mManager[instance].visibility).geometryType == GeometryType::DYNAMIC, + "This renderable has staticBounds enabled; its AABB cannot change."); mManager[instance].aabb = aabb; } } @@ -356,16 +385,26 @@ bool FRenderableManager::getFogEnabled(RenderableManager::Instance instance) con return getVisibility(instance).fog; } -void FRenderableManager::setSkinning(Instance instance, bool enable) noexcept { +void FRenderableManager::setSkinning(Instance instance, bool enable) { if (instance) { Visibility& visibility = mManager[instance].visibility; + + ASSERT_PRECONDITION( + visibility.geometryType != GeometryType::STATIC || !enable, + "Skinning can't be used with STATIC geometry"); + visibility.skinning = enable; } } -void FRenderableManager::setMorphing(Instance instance, bool enable) noexcept { +void FRenderableManager::setMorphing(Instance instance, bool enable) { if (instance) { Visibility& visibility = mManager[instance].visibility; + + ASSERT_PRECONDITION( + visibility.geometryType != GeometryType::STATIC || !enable, + "Morphing can't be used with STATIC geometry"); + visibility.morphing = enable; } } @@ -434,22 +473,22 @@ FRenderableManager::getInstancesInfo(Instance instance) const noexcept { } utils::Slice const& FRenderableManager::getRenderPrimitives( - Instance instance, uint8_t level) const noexcept { + Instance instance, UTILS_UNUSED uint8_t level) const noexcept { return mManager[instance].primitives; } utils::Slice& FRenderableManager::getRenderPrimitives( - Instance instance, uint8_t level) noexcept { + Instance instance, UTILS_UNUSED uint8_t level) noexcept { return mManager[instance].primitives; } utils::Slice const& FRenderableManager::getMorphTargets( - Instance instance, uint8_t level) const noexcept { + Instance instance, UTILS_UNUSED uint8_t level) const noexcept { return mManager[instance].morphTargets; } utils::Slice& FRenderableManager::getMorphTargets( - Instance instance, uint8_t level) noexcept { + Instance instance, UTILS_UNUSED uint8_t level) noexcept { return mManager[instance].morphTargets; } diff --git a/filament/src/components/TransformManager.cpp b/filament/src/components/TransformManager.cpp index 9324d5ac6ed..7806e41f02e 100644 --- a/filament/src/components/TransformManager.cpp +++ b/filament/src/components/TransformManager.cpp @@ -471,8 +471,7 @@ void FTransformManager::validateNode(UTILS_UNUSED_IN_RELEASE Instance i) noexcep } void FTransformManager::gc(utils::EntityManager& em) noexcept { - auto& manager = mManager; - manager.gc(em, 4, [this](Entity e) { + mManager.gc(em, [this](Entity e) { destroy(e); }); } diff --git a/filament/src/components/TransformManager.h b/filament/src/components/TransformManager.h index e7a674c8b0e..6e42103370c 100644 --- a/filament/src/components/TransformManager.h +++ b/filament/src/components/TransformManager.h @@ -50,7 +50,23 @@ class UTILS_PRIVATE FTransformManager : public TransformManager { } Instance getInstance(utils::Entity e) const noexcept { - return Instance(mManager.getInstance(e)); + return { mManager.getInstance(e) }; + } + + size_t getComponentCount() const noexcept { + return mManager.getComponentCount(); + } + + bool empty() const noexcept { + return mManager.empty(); + } + + utils::Entity getEntity(Instance i) const noexcept { + return mManager.getEntity(i); + } + + utils::Entity const* getEntities() const noexcept { + return mManager.getEntities(); } void setAccurateTranslationsEnabled(bool enable) noexcept; @@ -103,7 +119,7 @@ class UTILS_PRIVATE FTransformManager : public TransformManager { math::mat4 getTransformAccurate(Instance ci) const noexcept { math::mat4f const& local = mManager[ci].local; - math::float3 localTranslationLo = mManager[ci].localTranslationLo; + math::float3 const localTranslationLo = mManager[ci].localTranslationLo; math::mat4 r(local); r[3].xyz += localTranslationLo; return r; @@ -111,7 +127,7 @@ class UTILS_PRIVATE FTransformManager : public TransformManager { math::mat4 getWorldTransformAccurate(Instance ci) const noexcept { math::mat4f const& world = mManager[ci].world; - math::float3 worldTranslationLo = mManager[ci].worldTranslationLo; + math::float3 const worldTranslationLo = mManager[ci].worldTranslationLo; math::mat4 r(world); r[3].xyz += worldTranslationLo; return r; diff --git a/filament/src/details/Camera.cpp b/filament/src/details/Camera.cpp index 88bdb246e85..d5703586378 100644 --- a/filament/src/details/Camera.cpp +++ b/filament/src/details/Camera.cpp @@ -96,11 +96,11 @@ void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p, void UTILS_NOINLINE FCamera::setCustomEyeProjection(math::mat4 const* projection, size_t count, math::mat4 const& projectionForCulling, double near, double far) { - ASSERT_PRECONDITION(count >= CONFIG_STEREOSCOPIC_EYES, + const Engine::Config& config = mEngine.getConfig(); + ASSERT_PRECONDITION(count >= config.stereoscopicEyeCount, "All eye projections must be supplied together, count must be >= " - "CONFIG_STEREOSCOPIC_EYES(%d)", - CONFIG_STEREOSCOPIC_EYES); - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { + "config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount); + for (int i = 0; i < config.stereoscopicEyeCount; i++) { mEyeProjection[i] = projection[i]; } mProjectionForCulling = projectionForCulling; @@ -165,7 +165,8 @@ void UTILS_NOINLINE FCamera::setProjection(Camera::Projection projection, } math::mat4 FCamera::getProjectionMatrix(uint8_t eye) const noexcept { - assert_invariant(eye < CONFIG_STEREOSCOPIC_EYES); + UTILS_UNUSED_IN_RELEASE const Engine::Config& config = mEngine.getConfig(); + assert_invariant(eye < config.stereoscopicEyeCount); // This is where we transform the user clip-space (GL convention) to our virtual clip-space // (inverted DX convention) // Note that this math ends up setting the projection matrix' p33 to 0, which is where we're @@ -190,6 +191,13 @@ math::mat4 FCamera::getCullingProjectionMatrix() const noexcept { return m * mProjectionForCulling; } +const math::mat4& FCamera::getUserProjectionMatrix(uint8_t eyeId) const { + const Engine::Config& config = mEngine.getConfig(); + ASSERT_PRECONDITION(eyeId < config.stereoscopicEyeCount, + "eyeId must be < config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount); + return mEyeProjection[eyeId]; +} + void UTILS_NOINLINE FCamera::setModelMatrix(const mat4f& modelMatrix) noexcept { FTransformManager& transformManager = mEngine.getTransformManager(); transformManager.setTransform(transformManager.getInstance(mEntity), modelMatrix); @@ -201,8 +209,9 @@ void UTILS_NOINLINE FCamera::setModelMatrix(const mat4& modelMatrix) noexcept { } void UTILS_NOINLINE FCamera::setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model) { - ASSERT_PRECONDITION(eyeId < CONFIG_STEREOSCOPIC_EYES, - "eyeId must be < CONFIG_STEREOSCOPIC_EYES(%d)", CONFIG_STEREOSCOPIC_EYES); + const Engine::Config& config = mEngine.getConfig(); + ASSERT_PRECONDITION(eyeId < config.stereoscopicEyeCount, + "eyeId must be < config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount); mEyeFromView[eyeId] = inverse(model); } @@ -249,26 +258,29 @@ double FCamera::computeEffectiveFov(double fovInDegrees, double focusDistance) n return fov * math::d::RAD_TO_DEG; } +uint8_t FCamera::getStereoscopicEyeCount() const noexcept { + const Engine::Config& config = mEngine.getConfig(); + return config.stereoscopicEyeCount; +} + // ------------------------------------------------------------------------------------------------ -CameraInfo::CameraInfo(FCamera const& camera) noexcept { - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { - eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) }; - } - cullingProjection = mat4f{ camera.getCullingProjectionMatrix() }; - model = mat4f{ camera.getModelMatrix() }; - view = mat4f{ camera.getViewMatrix() }; - zn = (float)camera.getNear(); - zf = (float)camera.getCullingFar(); - ev100 = Exposure::ev100(camera); - f = (float)camera.getFocalLength(); - A = f / camera.getAperture(); - d = std::max(zn, camera.getFocusDistance()); +CameraInfo::CameraInfo(FCamera const& camera) noexcept + : CameraInfo(camera, {}, camera.getModelMatrix()) { +} + +CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept + : CameraInfo(camera, inWorldTransform, inWorldTransform * camera.getModelMatrix()) { +} + +CameraInfo::CameraInfo(FCamera const& camera, CameraInfo const& mainCameraInfo) noexcept + : CameraInfo(camera, mainCameraInfo.worldTransform, camera.getModelMatrix()) { } -CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept { - const mat4 modelMatrix{ inWorldTransform * camera.getModelMatrix() }; - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { +CameraInfo::CameraInfo(FCamera const& camera, + math::mat4 const& inWorldTransform, + math::mat4 const& modelMatrix) noexcept { + for (size_t i = 0; i < camera.getStereoscopicEyeCount(); i++) { eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) }; eyeFromView[i] = mat4f{ camera.getEyeFromViewMatrix(i) }; } diff --git a/filament/src/details/Camera.h b/filament/src/details/Camera.h index bab9f11326a..cb03363658f 100644 --- a/filament/src/details/Camera.h +++ b/filament/src/details/Camera.h @@ -85,11 +85,7 @@ class FCamera : public Camera { math::mat4 getEyeFromViewMatrix(uint8_t eye) const noexcept { return mEyeFromView[eye]; } // viewing projection matrix set by the user - const math::mat4& getUserProjectionMatrix(uint8_t eyeId) const { - ASSERT_PRECONDITION(eyeId < CONFIG_STEREOSCOPIC_EYES, - "eyeId must be < CONFIG_STEREOSCOPIC_EYES(%d)", CONFIG_STEREOSCOPIC_EYES); - return mEyeProjection[eyeId]; - } + const math::mat4& getUserProjectionMatrix(uint8_t eyeId) const; // culling projection matrix set by the user math::mat4 getUserCullingProjectionMatrix() const noexcept { return mProjectionForCulling; } @@ -189,6 +185,8 @@ class FCamera : public Camera { static double computeEffectiveFov(double fovInDegrees, double focusDistance) noexcept; + uint8_t getStereoscopicEyeCount() const noexcept; + utils::Entity getEntity() const noexcept { return mEntity; } @@ -204,10 +202,10 @@ class FCamera : public Camera { utils::Entity mEntity; // For monoscopic cameras, mEyeProjection[0] == mEyeProjection[1]. - math::mat4 mEyeProjection[CONFIG_STEREOSCOPIC_EYES]; // projection matrix per eye (infinite far) - math::mat4 mProjectionForCulling; // projection matrix (with far plane) - math::mat4 mEyeFromView[CONFIG_STEREOSCOPIC_EYES]; // transforms from the main view (head) - // space to each eye's unique view space + math::mat4 mEyeProjection[CONFIG_MAX_STEREOSCOPIC_EYES]; // projection matrix per eye (infinite far) + math::mat4 mProjectionForCulling; // projection matrix (with far plane) + math::mat4 mEyeFromView[CONFIG_MAX_STEREOSCOPIC_EYES]; // transforms from the main view (head) + // space to each eye's unique view space math::double2 mScalingCS = {1.0}; // additional scaling applied to projection math::double2 mShiftCS = {0.0}; // additional translation applied to projection @@ -222,9 +220,19 @@ class FCamera : public Camera { struct CameraInfo { CameraInfo() noexcept {} - explicit CameraInfo(FCamera const& camera) noexcept; + + // Creates a CameraInfo relative to inWorldTransform (i.e. it's model matrix is + // transformed by inWorldTransform and inWorldTransform is recorded). + // This is typically used for the color pass camera. CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept; + // Creates a CameraInfo from a camera that is relative to mainCameraInfo. + // This is typically used for the shadow pass cameras. + CameraInfo(FCamera const& camera, CameraInfo const& mainCameraInfo) noexcept; + + // Creates a CameraInfo from the FCamera + explicit CameraInfo(FCamera const& camera) noexcept; + union { // projection matrix for drawing (infinite zfar) // for monoscopic rendering @@ -232,15 +240,15 @@ struct CameraInfo { math::mat4f projection; // for stereo rendering, one matrix per eye - math::mat4f eyeProjection[CONFIG_STEREOSCOPIC_EYES] = {}; + math::mat4f eyeProjection[CONFIG_MAX_STEREOSCOPIC_EYES] = {}; }; - math::mat4f cullingProjection; // projection matrix for culling - math::mat4f model; // camera model matrix - math::mat4f view; // camera view matrix (inverse(model)) - math::mat4f eyeFromView[CONFIG_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic) - math::mat4 worldTransform; // world transform (already applied - // to model and view) + math::mat4f cullingProjection; // projection matrix for culling + math::mat4f model; // camera model matrix + math::mat4f view; // camera view matrix (inverse(model)) + math::mat4f eyeFromView[CONFIG_MAX_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic) + math::mat4 worldTransform; // world transform (already applied + // to model and view) math::float4 clipTransform{1, 1, 0, 0}; // clip-space transform, only for VERTEX_DOMAIN_DEVICE float zn{}; // distance (positive) to the near plane float zf{}; // distance (positive) to the far plane @@ -251,6 +259,11 @@ struct CameraInfo { math::float3 const& getPosition() const noexcept { return model[3].xyz; } math::float3 getForwardVector() const noexcept { return normalize(-model[2].xyz); } math::mat4 getUserViewMatrix() const noexcept { return view * worldTransform; } + +private: + CameraInfo(FCamera const& camera, + math::mat4 const& inWorldTransform, + math::mat4 const& modelMatrix) noexcept; }; FILAMENT_DOWNCAST(Camera) diff --git a/filament/src/details/ColorGrading.cpp b/filament/src/details/ColorGrading.cpp index 6bd40a41417..e02f029080e 100644 --- a/filament/src/details/ColorGrading.cpp +++ b/filament/src/details/ColorGrading.cpp @@ -30,11 +30,12 @@ #include #include -#include +#include #include #include #include +#include #include namespace filament { @@ -648,9 +649,9 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { Config c; // This lock protects the data inside Config, which is written to by the Filament thread, // and read from multiple Job threads. - utils::SpinLock configLock; + utils::Mutex configLock; { - std::lock_guard lock(configLock); + std::lock_guard const lock(configLock); c.lutDimension = builder->dimension; c.adaptationTransform = adaptationTransform(builder->whiteBalance); c.colorGradingIn = selectColorGradingTransformIn(builder->toneMapping); @@ -687,7 +688,7 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { [data, converted, b, &c, &configLock, builder](JobSystem&, JobSystem::Job*) { Config config; { - std::lock_guard lock(configLock); + std::lock_guard lock(configLock); config = c; } half4* UTILS_RESTRICT p = (half4*) data + b * config.lutDimension * config.lutDimension; diff --git a/filament/src/details/DebugRegistry.cpp b/filament/src/details/DebugRegistry.cpp index decd59610a9..ad1a54df74a 100644 --- a/filament/src/details/DebugRegistry.cpp +++ b/filament/src/details/DebugRegistry.cpp @@ -16,12 +16,18 @@ #include "details/DebugRegistry.h" +#include +#include #include #include #include #include +#include +#include +#include + #ifndef NDEBUG # define DEBUG_PROPERTIES_WRITABLE true #else @@ -120,12 +126,25 @@ void FDebugRegistry::registerDataSource(std::string_view name, } } +void FDebugRegistry::registerDataSource(std::string_view name, + utils::Invocable&& creator) noexcept { + mDataSourceCreatorMap[name] = std::move(creator); +} + DebugRegistry::DataSource FDebugRegistry::getDataSource(const char* name) const noexcept { std::string_view const key{ name }; auto& dataSourceMap = mDataSourceMap; auto const& it = dataSourceMap.find(key); - if (it == dataSourceMap.end()) { - return { nullptr, 0u }; + if (UTILS_UNLIKELY(it == dataSourceMap.end())) { + auto& dataSourceCreatorMap = mDataSourceCreatorMap; + auto const& pos = dataSourceCreatorMap.find(key); + if (pos == dataSourceCreatorMap.end()) { + return { nullptr, 0u }; + } + DataSource dataSource{ pos->second() }; + dataSourceMap[key] = dataSource; + dataSourceCreatorMap.erase(pos); + return dataSource; } return it->second; } diff --git a/filament/src/details/DebugRegistry.h b/filament/src/details/DebugRegistry.h index 44fc0818925..b60a1c69949 100644 --- a/filament/src/details/DebugRegistry.h +++ b/filament/src/details/DebugRegistry.h @@ -22,18 +22,27 @@ #include #include +#include + +#include #include #include #include #include +#include + namespace filament { class FEngine; class FDebugRegistry : public DebugRegistry { public: + enum Type { + BOOL, INT, FLOAT, FLOAT2, FLOAT3, FLOAT4 + }; + FDebugRegistry() noexcept; void registerProperty(std::string_view name, bool* p) noexcept { @@ -91,8 +100,13 @@ class FDebugRegistry : public DebugRegistry { registerProperty(name, p, FLOAT4, std::move(fn)); } + // registers a DataSource directly void registerDataSource(std::string_view name, void const* data, size_t count) noexcept; + // registers a DataSource lazily + void registerDataSource(std::string_view name, + utils::Invocable&& creator) noexcept; + #if !defined(_MSC_VER) private: #endif @@ -109,7 +123,8 @@ class FDebugRegistry : public DebugRegistry { void const* getPropertyAddress(const char* name) const noexcept; DataSource getDataSource(const char* name) const noexcept; std::unordered_map mPropertyMap; - std::unordered_map mDataSourceMap; + mutable std::unordered_map mDataSourceMap; + mutable std::unordered_map> mDataSourceCreatorMap; }; FILAMENT_DOWNCAST(DebugRegistry) diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index aa79e4146ee..ac1b3f92b9a 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -67,7 +67,9 @@ struct Engine::BuilderDetails { Backend mBackend = Backend::DEFAULT; Platform* mPlatform = nullptr; Engine::Config mConfig; + FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; void* mSharedContext = nullptr; + bool mPaused = false; static Config validateConfig(const Config* pConfig) noexcept; }; @@ -97,7 +99,11 @@ Engine* FEngine::create(Engine::Builder const& builder) { return nullptr; } DriverConfig const driverConfig{ - .handleArenaSize = instance->getRequestedDriverHandleArenaSize() }; + .handleArenaSize = instance->getRequestedDriverHandleArenaSize(), + .textureUseAfterFreePoolSize = instance->getConfig().textureUseAfterFreePoolSize, + .disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile, + .disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck + }; instance->mDriver = platform->createDriver(sharedContext, driverConfig); } else { @@ -185,6 +191,7 @@ static const uint16_t sFullScreenTriangleIndices[3] = { 0, 1, 2 }; FEngine::FEngine(Engine::Builder const& builder) : mBackend(builder->mBackend), + mActiveFeatureLevel(builder->mFeatureLevel), mPlatform(builder->mPlatform), mSharedGLContext(builder->mSharedContext), mPostProcessManager(*this), @@ -195,8 +202,9 @@ FEngine::FEngine(Engine::Builder const& builder) : mCameraManager(*this), mCommandBufferQueue( builder->mConfig.minCommandBufferSizeMB * MiB, - builder->mConfig.commandBufferSizeMB * MiB), - mPerRenderPassAllocator( + builder->mConfig.commandBufferSizeMB * MiB, + builder->mPaused), + mPerRenderPassArena( "FEngine::mPerRenderPassAllocator", builder->mConfig.perRenderPassArenaSizeMB * MiB), mHeapAllocator("FEngine::mHeapAllocator", AreaPolicy::NullArea{}), @@ -242,11 +250,15 @@ void FEngine::init() { mActiveFeatureLevel = std::min(mActiveFeatureLevel, driverApi.getFeatureLevel()); +#ifndef FILAMENT_ENABLE_FEATURE_LEVEL_0 + assert_invariant(mActiveFeatureLevel > FeatureLevel::FEATURE_LEVEL_0); +#endif + slog.i << "Backend feature level: " << int(driverApi.getFeatureLevel()) << io::endl; slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl; - mResourceAllocator = new ResourceAllocator(driverApi); + mResourceAllocator = new ResourceAllocator(mConfig, driverApi); mFullScreenTriangleVb = downcast(VertexBuffer::Builder() .vertexCount(3) @@ -267,7 +279,7 @@ void FEngine::init() { mFullScreenTriangleRph = driverApi.createRenderPrimitive( mFullScreenTriangleVb->getHwHandle(), mFullScreenTriangleIb->getHwHandle(), - PrimitiveType::TRIANGLES, 0, 0, 2, (uint32_t)mFullScreenTriangleIb->getIndexCount()); + PrimitiveType::TRIANGLES); // Compute a clip-space [-1 to 1] to texture space [0 to 1] matrix, taking into account // backend differences. @@ -326,11 +338,11 @@ void FEngine::init() { driverApi.update3DImage(mDummyZeroTexture, 0, 0, 0, 0, 1, 1, 1, { zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE }); -#ifdef FILAMENT_TARGET_MOBILE +#ifdef FILAMENT_ENABLE_FEATURE_LEVEL_0 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 @@ -338,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] = {}; @@ -359,21 +383,43 @@ void FEngine::init() { driverApi.update3DImage(mDummyZeroTextureArray, 0, 0, 0, 0, 1, 1, 1, { zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE }); - mPostProcessManager.init(); mLightManager.init(*this); mDFG.init(*this); } + mPostProcessManager.init(); + mDebugRegistry.registerProperty("d.shadowmap.debug_directional_shadowmap", &debug.shadowmap.debug_directional_shadowmap, [this]() { - mMaterials.forEach([](FMaterial* material) { + mMaterials.forEach([this](FMaterial* material) { if (material->getMaterialDomain() == MaterialDomain::SURFACE) { + + material->setConstant( + +ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP, + debug.shadowmap.debug_directional_shadowmap); + material->invalidate( Variant::DIR | Variant::SRE | Variant::DEP, Variant::DIR | Variant::SRE); } }); }); + + mDebugRegistry.registerProperty("d.lighting.debug_froxel_visualization", + &debug.lighting.debug_froxel_visualization, [this]() { + mMaterials.forEach([this](FMaterial* material) { + if (material->getMaterialDomain() == MaterialDomain::SURFACE) { + + material->setConstant( + +ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION, + debug.lighting.debug_froxel_visualization); + + material->invalidate( + Variant::DYN | Variant::DEP, + Variant::DYN); + } + }); + }); } FEngine::~FEngine() noexcept { @@ -413,7 +459,7 @@ void FEngine::shutdown() { mDFG.terminate(*this); // free-up the DFG mRenderableManager.terminate(); // free-up all renderables mLightManager.terminate(); // free-up all lights - mCameraManager.terminate(); // free-up all cameras + mCameraManager.terminate(*this); // free-up all cameras driver.destroyRenderPrimitive(mFullScreenTriangleRph); destroy(mFullScreenTriangleIb); @@ -526,7 +572,7 @@ void FEngine::gc() { mRenderableManager.gc(em); mLightManager.gc(em); mTransformManager.gc(em); - mCameraManager.gc(em); + mCameraManager.gc(*this, em); } void FEngine::flush() { @@ -621,7 +667,12 @@ int FEngine::loop() { JobSystem::setThreadName("FEngine::loop"); JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY); - DriverConfig const driverConfig { .handleArenaSize = getRequestedDriverHandleArenaSize() }; + DriverConfig const driverConfig { + .handleArenaSize = getRequestedDriverHandleArenaSize(), + .textureUseAfterFreePoolSize = mConfig.textureUseAfterFreePoolSize, + .disableParallelShaderCompile = mConfig.disableParallelShaderCompile, + .disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck + }; mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig); mDriverBarrier.latch(); @@ -675,10 +726,13 @@ const FMaterial* FEngine::getSkyboxMaterial() const noexcept { * Object created from a Builder */ -template -inline T* FEngine::create(ResourceList& list, typename T::Builder const& builder) noexcept { - T* p = mHeapAllocator.make(*this, builder); - list.insert(p); +template +inline T* FEngine::create(ResourceList& list, + typename T::Builder const& builder, ARGS&& ... args) noexcept { + T* p = mHeapAllocator.make(*this, builder, std::forward(args)...); + if (UTILS_UNLIKELY(p)) { // this should never happen + list.insert(p); + } return p; } @@ -714,8 +768,9 @@ FIndirectLight* FEngine::createIndirectLight(const IndirectLight::Builder& build return create(mIndirectLights, builder); } -FMaterial* FEngine::createMaterial(const Material::Builder& builder) noexcept { - return create(mMaterials, builder); +FMaterial* FEngine::createMaterial(const Material::Builder& builder, + std::unique_ptr materialParser) noexcept { + return create(mMaterials, builder, std::move(materialParser)); } FSkybox* FEngine::createSkybox(const Skybox::Builder& builder) noexcept { @@ -740,7 +795,7 @@ FRenderTarget* FEngine::createRenderTarget(const RenderTarget::Builder& builder) FRenderer* FEngine::createRenderer() noexcept { FRenderer* p = mHeapAllocator.make(*this); - if (p) { + if (UTILS_UNLIKELY(p)) { // should never happen mRenderers.insert(p); } return p; @@ -749,7 +804,7 @@ FRenderer* FEngine::createRenderer() noexcept { FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const FMaterialInstance* other, const char* name) noexcept { FMaterialInstance* p = mHeapAllocator.make(*this, other, name); - if (p) { + if (UTILS_UNLIKELY(p)) { // should never happen auto pos = mMaterialInstances.emplace(material, "MaterialInstance"); pos.first->second.insert(p); } @@ -762,7 +817,7 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, FScene* FEngine::createScene() noexcept { FScene* p = mHeapAllocator.make(*this); - if (p) { + if (UTILS_UNLIKELY(p)) { // should never happen mScenes.insert(p); } return p; @@ -770,7 +825,7 @@ FScene* FEngine::createScene() noexcept { FView* FEngine::createView() noexcept { FView* p = mHeapAllocator.make(*this); - if (p) { + if (UTILS_UNLIKELY(p)) { // should never happen mViews.insert(p); } return p; @@ -778,7 +833,7 @@ FView* FEngine::createView() noexcept { FFence* FEngine::createFence() noexcept { FFence* p = mHeapAllocator.make(*this); - if (p) { + if (UTILS_UNLIKELY(p)) { // should never happen std::lock_guard const guard(mFenceListLock); mFences.insert(p); } @@ -794,7 +849,7 @@ FSwapChain* FEngine::createSwapChain(void* nativeWindow, uint64_t flags) noexcep getDriverApi().setupExternalImage(nativeWindow); } FSwapChain* p = mHeapAllocator.make(*this, nativeWindow, flags); - if (p) { + if (UTILS_UNLIKELY(p)) { // should never happen mSwapChains.insert(p); } return p; @@ -814,7 +869,7 @@ FSwapChain* FEngine::createSwapChain(uint32_t width, uint32_t height, uint64_t f FCamera* FEngine::createCamera(Entity entity) noexcept { - return mCameraManager.create(entity); + return mCameraManager.create(*this, entity); } FCamera* FEngine::getCameraComponent(Entity entity) noexcept { @@ -823,7 +878,7 @@ FCamera* FEngine::getCameraComponent(Entity entity) noexcept { } void FEngine::destroyCameraComponent(utils::Entity entity) noexcept { - mCameraManager.destroy(entity); + mCameraManager.destroy(*this, entity); } @@ -1039,7 +1094,7 @@ void FEngine::destroy(Entity e) { mRenderableManager.destroy(e); mLightManager.destroy(e); mTransformManager.destroy(e); - mCameraManager.destroy(e); + mCameraManager.destroy(*this, e); } bool FEngine::isValid(const FBufferObject* p) { @@ -1149,6 +1204,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(); @@ -1157,6 +1216,8 @@ Engine::FeatureLevel FEngine::getSupportedFeatureLevel() const noexcept { Engine::FeatureLevel FEngine::setActiveFeatureLevel(FeatureLevel featureLevel) { ASSERT_PRECONDITION(featureLevel <= getSupportedFeatureLevel(), "Feature level %u not supported", (unsigned)featureLevel); + ASSERT_PRECONDITION(mActiveFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1, + "Cannot adjust feature level beyond 0 at runtime"); return (mActiveFeatureLevel = std::max(mActiveFeatureLevel, featureLevel)); } @@ -1190,11 +1251,21 @@ Engine::Builder& Engine::Builder::config(Engine::Config const* config) noexcept return *this; } +Engine::Builder& Engine::Builder::featureLevel(FeatureLevel featureLevel) noexcept { + mImpl->mFeatureLevel = featureLevel; + return *this; +} + Engine::Builder& Engine::Builder::sharedContext(void* sharedContext) noexcept { mImpl->mSharedContext = sharedContext; 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 { @@ -1245,6 +1316,9 @@ Engine::Config Engine::BuilderDetails::validateConfig(const Config* const pConfi // This value gets validated during driver creation, so pass it through config.driverHandleArenaSizeMB = config.driverHandleArenaSizeMB; + config.stereoscopicEyeCount = + std::clamp(config.stereoscopicEyeCount, uint8_t(1), CONFIG_MAX_STEREOSCOPIC_EYES); + return config; } diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 0ae8913667c..b467013b995 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -23,6 +23,7 @@ #include "DFG.h" #include "PostProcessManager.h" #include "ResourceList.h" +#include "HwVertexBufferInfoFactory.h" #include "components/CameraManager.h" #include "components/LightManager.h" @@ -58,17 +59,6 @@ #include #include -#if FILAMENT_ENABLE_MATDBG -#include -#else -namespace filament { -namespace matdbg { -class DebugServer; -using MaterialKey = uint32_t; -} // namespace matdbg -} // namespace filament -#endif - #include #include #include @@ -78,8 +68,19 @@ using MaterialKey = uint32_t; #include #include #include +#include +#include #include +#if FILAMENT_ENABLE_MATDBG +#include +#else +namespace filament::matdbg { +class DebugServer; +using MaterialKey = uint32_t; +} // namespace filament::matdbg +#endif + namespace filament { class Renderer; @@ -142,7 +143,7 @@ class FEngine : public Engine { // the per-frame Area is used by all Renderer, so they must run in sequence and // have freed all allocated memory when done. If this needs to change in the future, // we'll simply have to use separate Areas (for instance). - LinearAllocatorArena& getPerRenderPassAllocator() noexcept { return mPerRenderPassAllocator; } + LinearAllocatorArena& getPerRenderPassArena() noexcept { return mPerRenderPassArena; } // Material IDs... uint32_t getMaterialId() const noexcept { return mMaterialId++; } @@ -182,7 +183,13 @@ class FEngine : public Engine { return CONFIG_MAX_INSTANCES; } - bool isStereoSupported() const noexcept { return getDriver().isStereoSupported(); } + bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept { + return getDriver().isStereoSupported(stereoscopicType); + } + + static size_t getMaxStereoscopicEyes() noexcept { + return CONFIG_MAX_STEREOSCOPIC_EYES; + } PostProcessManager const& getPostProcessManager() const noexcept { return mPostProcessManager; @@ -231,7 +238,7 @@ class FEngine : public Engine { default: return backend::ShaderLanguage::ESSL3; case Backend::OPENGL: - return mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0 + return getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0 ? backend::ShaderLanguage::ESSL1 : backend::ShaderLanguage::ESSL3; case Backend::VULKAN: return backend::ShaderLanguage::SPIRV; @@ -256,8 +263,8 @@ class FEngine : public Engine { return mDefaultRenderTarget; } - template - T* create(ResourceList& list, typename T::Builder const& builder) noexcept; + template + T* create(ResourceList& list, typename T::Builder const& builder, ARGS&& ... args) noexcept; FBufferObject* createBufferObject(const BufferObject::Builder& builder) noexcept; FVertexBuffer* createVertexBuffer(const VertexBuffer::Builder& builder) noexcept; @@ -266,7 +273,7 @@ class FEngine : public Engine { FMorphTargetBuffer* createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept; FInstanceBuffer* createInstanceBuffer(const InstanceBuffer::Builder& builder) noexcept; FIndirectLight* createIndirectLight(const IndirectLight::Builder& builder) noexcept; - FMaterial* createMaterial(const Material::Builder& builder) noexcept; + FMaterial* createMaterial(const Material::Builder& builder, std::unique_ptr materialParser) noexcept; FTexture* createTexture(const Texture::Builder& builder) noexcept; FSkybox* createSkybox(const Skybox::Builder& builder) noexcept; FColorGrading* createColorGrading(const ColorGrading::Builder& builder) noexcept; @@ -333,6 +340,8 @@ class FEngine : public Engine { void destroy(utils::Entity e); + void setPaused(bool paused); + void flushAndWait(); // flush the current buffer @@ -399,6 +408,10 @@ class FEngine : public Engine { return mAutomaticInstancingEnabled; } + HwVertexBufferInfoFactory& getVertexBufferInfoFactory() noexcept { + return mHwVertexBufferInfoFactory; + } + backend::Handle getOneTexture() const { return mDummyOneTexture; } backend::Handle getZeroTexture() const { return mDummyZeroTexture; } backend::Handle getOneTextureArray() const { return mDummyOneTextureArray; } @@ -467,6 +480,7 @@ class FEngine : public Engine { FLightManager mLightManager; FCameraManager mCameraManager; ResourceAllocator* mResourceAllocator = nullptr; + HwVertexBufferInfoFactory mHwVertexBufferInfoFactory; ResourceList mBufferObjects{ "BufferObject" }; ResourceList mRenderers{ "Renderer" }; @@ -487,7 +501,7 @@ class FEngine : public Engine { ResourceList mRenderTargets{ "RenderTarget" }; // the fence list is accessed from multiple threads - utils::SpinLock mFenceListLock; + utils::Mutex mFenceListLock; ResourceList mFences{"Fence"}; mutable uint32_t mMaterialId = 0; @@ -504,7 +518,7 @@ class FEngine : public Engine { uint32_t mFlushCounter = 0; - LinearAllocatorArena mPerRenderPassAllocator; + RootArenaScope::Arena mPerRenderPassArena; HeapAllocatorArena mHeapAllocator; utils::JobSystem mJobSystem; @@ -544,12 +558,20 @@ class FEngine : public Engine { struct { struct { bool debug_directional_shadowmap = false; + bool display_shadow_texture = false; bool far_uses_shadowcasters = true; bool focus_shadowcasters = true; bool visualize_cascades = false; bool tightly_bound_scene = true; float dzn = -1.0f; float dzf = 1.0f; + float display_shadow_texture_scale = 0.25f; + int display_shadow_texture_layer = 0; + int display_shadow_texture_level = 0; + int display_shadow_texture_channel = 0; + int display_shadow_texture_layer_count = 0; + int display_shadow_texture_level_count = 0; + float display_shadow_texture_power = 1; } shadowmap; struct { bool camera_at_origin = true; @@ -565,6 +587,12 @@ class FEngine : public Engine { bool doFrameCapture = false; bool disable_buffer_padding = false; } renderer; + 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/Material.cpp b/filament/src/details/Material.cpp index 72a9e808106..dbb4339f465 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -15,29 +15,58 @@ */ #include "details/Material.h" +#include "details/Engine.h" #include "Froxelizer.h" #include "MaterialParser.h" -#include "details/Engine.h" - #include "FilamentAPI-impl.h" -#include - #include #include #include +#include + +#include +#include + +#if FILAMENT_ENABLE_MATDBG +#include +#endif + +#include +#include #include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include + +#include +#include namespace filament { @@ -45,19 +74,21 @@ using namespace backend; using namespace filaflat; using namespace utils; -static MaterialParser* createParser(Backend backend, ShaderLanguage language, - const void* data, size_t size) { +static std::unique_ptr createParser( + Backend backend, ShaderLanguage language, const void* data, size_t size) { // unique_ptr so we don't leak MaterialParser on failures below auto materialParser = std::make_unique(language, data, size); MaterialParser::ParseResult const materialResult = materialParser->parse(); if (backend == Backend::NOOP) { - return materialParser.release(); + return materialParser; } ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND, - "the material was not built for the %s backend\n", backendToString(backend)); + "the material was not built for the %s backend and %s shader language\n", + backendToString(backend), + shaderLanguageToString(language)); ASSERT_PRECONDITION(materialResult == MaterialParser::ParseResult::SUCCESS, "could not parse the material package"); @@ -69,15 +100,17 @@ static MaterialParser* createParser(Backend backend, ShaderLanguage language, assert_invariant(backend != Backend::DEFAULT && "Default backend has not been resolved."); - return materialParser.release(); + return materialParser; } struct Material::BuilderDetails { const void* mPayload = nullptr; size_t mSize = 0; - MaterialParser* mMaterialParser = nullptr; bool mDefaultMaterial = false; - std::unordered_map> mConstantSpecializations; + std::unordered_map< + utils::CString, + std::variant, + CString::Hasher> mConstantSpecializations; }; FMaterial::DefaultMaterialBuilder::DefaultMaterialBuilder() : Material::Builder() { @@ -110,11 +143,11 @@ template Material::Builder& Material::Builder::constant(const char*, size template Material::Builder& Material::Builder::constant(const char*, size_t, bool); Material* Material::Builder::build(Engine& engine) { - std::unique_ptr materialParser{ createParser( + std::unique_ptr materialParser = createParser( downcast(engine).getBackend(), downcast(engine).getShaderLanguage(), - mImpl->mPayload, mImpl->mSize) }; + mImpl->mPayload, mImpl->mSize); - if (materialParser == nullptr) { + if (!materialParser) { return nullptr; } @@ -141,17 +174,16 @@ Material* Material::Builder::build(Engine& engine) { return nullptr; } - mImpl->mMaterialParser = materialParser.release(); - - return downcast(engine).createMaterial(*this); + return downcast(engine).createMaterial(*this, std::move(materialParser)); } -FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) +FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder, + std::unique_ptr materialParser) : mEngine(engine), - mMaterialId(engine.getMaterialId()) + mMaterialId(engine.getMaterialId()), + mMaterialParser(std::move(materialParser)) { - MaterialParser* parser = builder->mMaterialParser; - mMaterialParser = parser; + MaterialParser* const parser = mMaterialParser.get(); UTILS_UNUSED_IN_RELEASE bool const nameOk = parser->getName(&mName); assert_invariant(nameOk); @@ -184,7 +216,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) success = parser->getUIB(&mUniformInterfaceBlock); assert_invariant(success); - if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) { + if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) { success = parser->getBindingUniformInfo(&mBindingUniformInfo); assert_invariant(success); @@ -214,9 +246,14 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mSubpassInfo.isValid = false; } - utils::FixedCapacityVector constants; // Older materials won't have a constants chunk, but that's okay. - parser->getConstants(&constants); + parser->getConstants(&mMaterialConstants); + for (size_t i = 0, c = mMaterialConstants.size(); i < c; i++) { + auto& item = mMaterialConstants[i]; + // the key can be a string_view because mMaterialConstant owns the CString + std::string_view const key{ item.name.data(), item.name.size() }; + mSpecializationConstantsNameToIndex[key] = i; + } // Verify that all the constant specializations exist in the material and that their types match. // The first specialization constants are defined internally by Filament. @@ -226,7 +263,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) int const maxInstanceCount = (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) ? 1 : CONFIG_MAX_INSTANCES; - int const maxFroxelBufferHeight = std::min( + int const maxFroxelBufferHeight = (int)std::min( FROXEL_BUFFER_MAX_ENTRY_COUNT / 4, engine.getDriverApi().getMaxUniformBufferSize() / 16u); @@ -236,7 +273,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) bool const powerVrShaderWorkarounds = engine.getDriverApi().isWorkaroundNeeded(Workaround::POWER_VR_SHADER_WORKAROUNDS); - mSpecializationConstants.reserve(constants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); + mSpecializationConstants.reserve(mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); mSpecializationConstants.push_back({ +ReservedSpecializationConstants::BACKEND_FEATURE_LEVEL, (int)engine.getSupportedFeatureLevel() }); @@ -248,33 +285,38 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) (int)maxFroxelBufferHeight }); mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP, - (bool)engine.debug.shadowmap.debug_directional_shadowmap }); + engine.debug.shadowmap.debug_directional_shadowmap }); + mSpecializationConstants.push_back({ + +ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION, + engine.debug.lighting.debug_froxel_visualization }); mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND, - (bool)staticTextureWorkaround }); + staticTextureWorkaround }); mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, - (bool)powerVrShaderWorkarounds }); - if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) { + powerVrShaderWorkarounds }); + mSpecializationConstants.push_back({ + +ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, + (int)engine.getConfig().stereoscopicEyeCount }); + if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) { // The actual value of this spec-constant is set in the OpenGLDriver backend. mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION, false}); } - for (const auto& [name, value] : builder->mConstantSpecializations) { - auto found = std::find_if( - constants.begin(), constants.end(), [name = name](const auto& constant) { - return strncmp(constant.name.data(), name.data(), name.length()) == 0; - }); - ASSERT_PRECONDITION(found != constants.end(), + for (auto const& [name, value] : builder->mConstantSpecializations) { + std::string_view const key{ name.data(), name.size() }; + auto pos = mSpecializationConstantsNameToIndex.find(key); + ASSERT_PRECONDITION(pos != mSpecializationConstantsNameToIndex.end(), "The material %s does not have a constant parameter named %s.", mName.c_str_safe(), name.c_str()); const char* const types[3] = {"an int", "a float", "a bool"}; const char* const errorMessage = "The constant parameter %s on material %s is of type %s, but %s was " "provided."; - switch (found->type) { + auto& constant = mMaterialConstants[pos->second]; + switch (constant.type) { case ConstantType::INT: ASSERT_PRECONDITION(std::holds_alternative(value), errorMessage, name.c_str(), mName.c_str_safe(), "int", types[value.index()]); @@ -288,8 +330,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) name.c_str(), mName.c_str_safe(), "bool", types[value.index()]); break; } - uint32_t const index = - std::distance(constants.begin(), found) + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + uint32_t const index = pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; mSpecializationConstants.push_back({ index, value }); } @@ -309,12 +350,8 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) parser->getMaskThreshold(&mMaskThreshold); } - // The fade blending mode only affects shading. For proper sorting we need to - // treat this blending mode as a regular transparent blending operation. - if (UTILS_UNLIKELY(mBlendingMode == BlendingMode::FADE)) { - mRenderBlendingMode = BlendingMode::TRANSPARENT; - } else { - mRenderBlendingMode = mBlendingMode; + if (mBlendingMode == BlendingMode::CUSTOM) { + parser->getCustomBlendFunction(&mCustomBlendFunctions); } if (mShading == Shading::UNLIT) { @@ -367,6 +404,12 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mRasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_COLOR; mRasterState.depthWrite = false; break; + case BlendingMode::CUSTOM: + mRasterState.blendFunctionSrcRGB = mCustomBlendFunctions[0]; + mRasterState.blendFunctionSrcAlpha = mCustomBlendFunctions[1]; + mRasterState.blendFunctionDstRGB = mCustomBlendFunctions[2]; + mRasterState.blendFunctionDstAlpha = mCustomBlendFunctions[3]; + mRasterState.depthWrite = false; } bool depthWriteSet = false; @@ -397,6 +440,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mIsDefaultMaterial = builder->mDefaultMaterial; if (UTILS_UNLIKELY(mIsDefaultMaterial)) { + assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); filaflat::MaterialChunk const& materialChunk{ mMaterialParser->getMaterialChunk() }; auto variants = FixedCapacityVector::with_capacity(materialChunk.getShaderCount()); materialChunk.visitShaders([&variants]( @@ -412,12 +456,14 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) std::swap(mDepthVariants, variants); } - if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) { - FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); - auto& cachedPrograms = mCachedPrograms; - for (Variant const variant : pDefaultMaterial->mDepthVariants) { - pDefaultMaterial->prepareProgram(variant); - cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant); + if (mMaterialDomain == MaterialDomain::SURFACE) { + if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) { + FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial(); + auto& cachedPrograms = mCachedPrograms; + for (Variant const variant: pDefaultMaterial->mDepthVariants) { + pDefaultMaterial->prepareProgram(variant); + cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant); + } } } @@ -446,49 +492,50 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mDefaultInstance.initDefaultInstance(engine, this); } -FMaterial::~FMaterial() noexcept { - delete mMaterialParser; -} +FMaterial::~FMaterial() noexcept = default; void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept { - // update the spec constants that can change - // TODO: should we just always update all of them? - auto pos = std::find_if(mSpecializationConstants.begin(), mSpecializationConstants.end(), - [&](const auto& item) { - return item.id == ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP; - }); - if (pos != mSpecializationConstants.end()) { - pos->value = mEngine.debug.shadowmap.debug_directional_shadowmap; - } - - DriverApi& driverApi = mEngine.getDriverApi(); - auto& cachedPrograms = mCachedPrograms; - for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) { - Variant const variant(k); - if ((k & variantMask) == variantValue) { - if (UTILS_LIKELY(!mIsDefaultMaterial)) { - // The depth variants may be shared with the default material, in which case - // we should not free it now. - bool const isSharedVariant = - Variant::isValidDepthVariant(variant) && !mHasCustomDepthShader; - if (isSharedVariant) { - // we don't own this variant, skip. - continue; + if (mMaterialDomain == MaterialDomain::SURFACE) { + DriverApi& driverApi = mEngine.getDriverApi(); + auto& cachedPrograms = mCachedPrograms; + for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) { + Variant const variant(k); + if ((k & variantMask) == variantValue) { + if (UTILS_LIKELY(!mIsDefaultMaterial)) { + // The depth variants may be shared with the default material, in which case + // we should not free it now. + bool const isSharedVariant = + Variant::isValidDepthVariant(variant) && !mHasCustomDepthShader; + if (isSharedVariant) { + // we don't own this variant, skip. + continue; + } } + driverApi.destroyProgram(cachedPrograms[k]); + cachedPrograms[k].clear(); } - driverApi.destroyProgram(cachedPrograms[k]); - cachedPrograms[k].clear(); } - } - if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) { - FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial(); - for (Variant const variant: pDefaultMaterial->mDepthVariants) { - pDefaultMaterial->prepareProgram(variant); - if (!cachedPrograms[variant.key]) { - cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant); + if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) { + FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial(); + for (Variant const variant: pDefaultMaterial->mDepthVariants) { + pDefaultMaterial->prepareProgram(variant); + if (!cachedPrograms[variant.key]) { + cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant); + } + } + } + } else if (mMaterialDomain == MaterialDomain::POST_PROCESS) { + DriverApi& driverApi = mEngine.getDriverApi(); + auto& cachedPrograms = mCachedPrograms; + for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; k < n; ++k) { + if ((k & variantMask) == variantValue) { + driverApi.destroyProgram(cachedPrograms[k]); + cachedPrograms[k].clear(); } } + } else if (mMaterialDomain == MaterialDomain::COMPUTE) { + // TODO: handle compute variants if any } } @@ -512,7 +559,8 @@ void FMaterial::compile(CompilerPriorityQueue priority, utils::Invocable&& callback) noexcept { // Turn off the STE variant if stereo is not supported. - if (!mEngine.getDriverApi().isStereoSupported()) { + const StereoscopicType stereoscopicType = mEngine.getConfig().stereoscopicType; + if (!mEngine.getDriverApi().isStereoSupported(stereoscopicType)) { variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE); } @@ -620,6 +668,9 @@ void FMaterial::getSurfaceProgramSlow(Variant variant, Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant) }; pb.priorityQueue(priorityQueue); + pb.multiview( + mEngine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW && + Variant::isStereoVariant(variant)); createAndCacheProgram(std::move(pb), variant); } @@ -637,7 +688,6 @@ Program FMaterial::getProgramWithVariants( FEngine const& engine = mEngine; const ShaderModel sm = engine.getShaderModel(); const bool isNoop = engine.getBackend() == Backend::NOOP; - const FeatureLevel engineFeatureLevel = engine.getActiveFeatureLevel(); /* * Vertex shader */ @@ -691,7 +741,7 @@ Program FMaterial::getProgramWithVariants( } } - if (engineFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { + if (UTILS_UNLIKELY(mEngine.getShaderLanguage() == ShaderLanguage::ESSL1)) { assert_invariant(!mBindingUniformInfo.empty()); for (auto const& [index, uniforms] : mBindingUniformInfo) { program.uniforms(uint32_t(index), uniforms); @@ -765,9 +815,7 @@ void FMaterial::applyPendingEdits() noexcept { const char* name = mName.c_str(); slog.d << "Applying edits to " << (name ? name : "(untitled)") << io::endl; destroyPrograms(mEngine); // FIXME: this will not destroy the shared variants - delete mMaterialParser; - mMaterialParser = mPendingEdits; - mPendingEdits = nullptr; + latchPendingEdits(); } /** @@ -784,8 +832,9 @@ void FMaterial::onEditCallback(void* userdata, const utils::CString&, const void // This is called on a web server thread, so we defer clearing the program cache // and swapping out the MaterialParser until the next getProgram call. - material->mPendingEdits = createParser(engine.getBackend(), engine.getShaderLanguage(), - packageData, packageSize); + std::unique_ptr pending = createParser( + engine.getBackend(), engine.getShaderLanguage(), packageData, packageSize); + material->setPendingEdits(std::move(pending)); } void FMaterial::onQueryCallback(void* userdata, VariantList* pVariants) { @@ -818,4 +867,55 @@ void FMaterial::destroyPrograms(FEngine& engine) { } } +std::optional FMaterial::getSpecializationConstantId(std::string_view name) const noexcept { + auto pos = mSpecializationConstantsNameToIndex.find(name); + if (pos != mSpecializationConstantsNameToIndex.end()) { + return pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + } + return std::nullopt; +} + +template +bool FMaterial::setConstant(uint32_t id, T value) noexcept { + size_t const maxId = mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS; + if (UTILS_LIKELY(id < maxId)) { + if (id >= CONFIG_MAX_RESERVED_SPEC_CONSTANTS) { + // Constant from the material itself (as opposed to the reserved ones) + auto& constant = mMaterialConstants[id - CONFIG_MAX_RESERVED_SPEC_CONSTANTS]; + using ConstantType = backend::ConstantType; + switch (constant.type) { + case ConstantType::INT: + if (!std::is_same_v) return false; + break; + case ConstantType::FLOAT: + if (!std::is_same_v) return false; + break; + case ConstantType::BOOL: + if (!std::is_same_v) return false; + break; + } + } + + auto pos = std::find_if( + mSpecializationConstants.begin(), mSpecializationConstants.end(), + [id](backend::Program::SpecializationConstant const& specializationConstant) { + return specializationConstant.id == id; + }); + if (pos != mSpecializationConstants.end()) { + if (std::get(pos->value) != value) { + pos->value = value; + return true; + } + } else { + mSpecializationConstants.push_back({ id, value }); + return true; + } + } + return false; +} + +template bool FMaterial::setConstant(uint32_t id, int32_t value) noexcept; +template bool FMaterial::setConstant(uint32_t id, float value) noexcept; +template bool FMaterial::setConstant(uint32_t id, bool value) noexcept; + } // namespace filament diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index fc1c6a8102d..2a2819df6a7 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -22,17 +22,39 @@ #include "details/MaterialInstance.h" #include +#include +#include +#include #include #include #include #include #include +#include +#include +#include +#include + #include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include + +#include +#include namespace filament { @@ -42,7 +64,8 @@ class FEngine; class FMaterial : public Material { public: - FMaterial(FEngine& engine, const Material::Builder& builder); + FMaterial(FEngine& engine, const Material::Builder& builder, + std::unique_ptr materialParser); ~FMaterial() noexcept; class DefaultMaterialBuilder : public Material::Builder { @@ -126,7 +149,6 @@ class FMaterial : public Material { Shading getShading() const noexcept { return mShading; } Interpolation getInterpolation() const noexcept { return mInterpolation; } BlendingMode getBlendingMode() const noexcept { return mBlendingMode; } - BlendingMode getRenderBlendingMode() const noexcept { return mRenderBlendingMode; } VertexDomain getVertexDomain() const noexcept { return mVertexDomain; } MaterialDomain getMaterialDomain() const noexcept { return mMaterialDomain; } CullingMode getCullingMode() const noexcept { return mCullingMode; } @@ -165,6 +187,14 @@ class FMaterial : public Material { void destroyPrograms(FEngine& engine); + // return the id of a specialization constant specified by name for this material + std::optional getSpecializationConstantId(std::string_view name) const noexcept ; + + // Sets a specialization constant by id. call is no-op if the id is invalid. + // Return true is the value was changed. + template> + bool setConstant(uint32_t id, T value) noexcept; + #if FILAMENT_ENABLE_MATDBG void applyPendingEdits() noexcept; @@ -189,7 +219,7 @@ class FMaterial : public Material { static void onQueryCallback(void* userdata, VariantList* pActiveVariants); void checkProgramEdits() noexcept { - if (UTILS_UNLIKELY(mPendingEdits.load())) { + if (UTILS_UNLIKELY(hasPendingEdits())) { applyPendingEdits(); } } @@ -214,13 +244,13 @@ class FMaterial : public Material { mutable std::array, VARIANT_COUNT> mCachedPrograms; backend::RasterState mRasterState; - BlendingMode mRenderBlendingMode = BlendingMode::OPAQUE; TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; bool mIsVariantLit = false; backend::FeatureLevel mFeatureLevel = backend::FeatureLevel::FEATURE_LEVEL_1; Shading mShading = Shading::UNLIT; BlendingMode mBlendingMode = BlendingMode::OPAQUE; + std::array mCustomBlendFunctions = {}; Interpolation mInterpolation = Interpolation::SMOOTH; VertexDomain mVertexDomain = VertexDomain::OBJECT; MaterialDomain mMaterialDomain = MaterialDomain::SURFACE; @@ -261,13 +291,32 @@ class FMaterial : public Material { SamplerGroupBindingInfoList mSamplerGroupBindingInfoList; SamplerBindingToNameMap mSamplerBindingToNameMap; + // Constants defined by this Material + utils::FixedCapacityVector mMaterialConstants; + // A map from the Constant name to the mMaterialConstant index + std::unordered_map mSpecializationConstantsNameToIndex; + // current specialization constants for the HwProgram utils::FixedCapacityVector mSpecializationConstants; #if FILAMENT_ENABLE_MATDBG matdbg::MaterialKey mDebuggerId; mutable utils::Mutex mActiveProgramsLock; mutable VariantList mActivePrograms; - std::atomic mPendingEdits = {}; + mutable utils::Mutex mPendingEditsLock; + std::unique_ptr mPendingEdits; + void setPendingEdits(std::unique_ptr pendingEdits) noexcept { + std::lock_guard lock(mPendingEditsLock); + std::swap(pendingEdits, mPendingEdits); + } + bool hasPendingEdits() noexcept { + std::lock_guard lock(mPendingEditsLock); + return (bool)mPendingEdits; + } + void latchPendingEdits() noexcept { + std::lock_guard lock(mPendingEditsLock); + std::swap(mPendingEdits, mMaterialParser); + } + #endif utils::CString mName; @@ -275,7 +324,7 @@ class FMaterial : public Material { const uint32_t mMaterialId; uint64_t mCacheId = 0; mutable uint32_t mMaterialInstanceId = 0; - MaterialParser* mMaterialParser = nullptr; + std::unique_ptr mMaterialParser; }; diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index c23915c6d22..9c8a88a30ba 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -73,7 +73,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, if (!material->getSamplerInterfaceBlock().isEmpty()) { mSamplers = other->getSamplerGroup(); - mSbHandle = driver.createSamplerGroup(mSamplers.getSize()); + mSbHandle = driver.createSamplerGroup( + mSamplers.getSize(), utils::FixedSizeString<32>(mMaterial->getName().c_str_safe())); } if (material->hasDoubleSidedCapability()) { @@ -115,7 +116,8 @@ void FMaterialInstance::initDefaultInstance(FEngine& engine, FMaterial const* ma if (!material->getSamplerInterfaceBlock().isEmpty()) { mSamplers = SamplerGroup(material->getSamplerInterfaceBlock().getSize()); - mSbHandle = driver.createSamplerGroup(mSamplers.getSize()); + mSbHandle = driver.createSamplerGroup( + mSamplers.getSize(), utils::FixedSizeString<32>(mMaterial->getName().c_str_safe())); } const RasterState& rasterState = material->getRasterState(); @@ -267,7 +269,7 @@ const char* FMaterialInstance::getName() const noexcept { // the instance's CString rather than calling empty(). This allows instances to override the // parent material's name with a blank string. if (mName.data() == nullptr) { - return mMaterial->getName().c_str(); + return mMaterial->getName().c_str_safe(); } return mName.c_str(); } diff --git a/filament/src/details/MorphTargetBuffer.cpp b/filament/src/details/MorphTargetBuffer.cpp index 6102b3c1169..b615e6e7054 100644 --- a/filament/src/details/MorphTargetBuffer.cpp +++ b/filament/src/details/MorphTargetBuffer.cpp @@ -125,7 +125,8 @@ FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder) TextureUsage::DEFAULT); // create and update sampler group - mSbHandle = driver.createSamplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT); + mSbHandle = driver.createSamplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT, + utils::FixedSizeString<32>("Morph target samplers")); SamplerGroup samplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT); samplerGroup.setSampler(PerRenderPrimitiveMorphingSib::POSITIONS, { mPbHandle, {}}); samplerGroup.setSampler(PerRenderPrimitiveMorphingSib::TANGENTS, { mTbHandle, {}}); 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 4ee8e2b2054..493060cd6aa 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -16,6 +16,9 @@ #include "details/Renderer.h" +#include "Allocators.h" +#include "DebugRegistry.h" +#include "FrameHistory.h" #include "PostProcessManager.h" #include "RendererUtils.h" #include "RenderPass.h" @@ -28,21 +31,44 @@ #include "details/Texture.h" #include "details/View.h" +#include +#include +#include #include +#include +#include +#include #include #include "fg/FrameGraph.h" #include "fg/FrameGraphId.h" #include "fg/FrameGraphResources.h" +#include "fg/FrameGraphTexture.h" + +#include +#include +#include #include #include +#include +#include #include #include -#include #include +#include +#include +#include + +#include +#include + +#ifdef __ANDROID__ +#include +#endif + // this helps visualize what dynamic-scaling is doing #define DEBUG_DYNAMIC_SCALING false @@ -62,14 +88,31 @@ FRenderer::FRenderer(FEngine& engine) : mHdrQualityMedium(TextureFormat::R11F_G11F_B10F), mHdrQualityHigh(TextureFormat::RGB16F), mIsRGB8Supported(false), - mUserEpoch(engine.getEngineEpoch()), - mPerRenderPassArena(engine.getPerRenderPassAllocator()) + mUserEpoch(engine.getEngineEpoch()) { FDebugRegistry& debugRegistry = engine.getDebugRegistry(); debugRegistry.registerProperty("d.renderer.doFrameCapture", &engine.debug.renderer.doFrameCapture); debugRegistry.registerProperty("d.renderer.disable_buffer_padding", &engine.debug.renderer.disable_buffer_padding); + debugRegistry.registerProperty("d.shadowmap.display_shadow_texture", + &engine.debug.shadowmap.display_shadow_texture); + debugRegistry.registerProperty("d.shadowmap.display_shadow_texture_scale", + &engine.debug.shadowmap.display_shadow_texture_scale); + debugRegistry.registerProperty("d.shadowmap.display_shadow_texture_layer", + &engine.debug.shadowmap.display_shadow_texture_layer); + debugRegistry.registerProperty("d.shadowmap.display_shadow_texture_level", + &engine.debug.shadowmap.display_shadow_texture_level); + debugRegistry.registerProperty("d.shadowmap.display_shadow_texture_channel", + &engine.debug.shadowmap.display_shadow_texture_channel); + debugRegistry.registerProperty("d.shadowmap.display_shadow_texture_layer_count", + &engine.debug.shadowmap.display_shadow_texture_layer_count); + debugRegistry.registerProperty("d.shadowmap.display_shadow_texture_level_count", + &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(); @@ -192,6 +235,21 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN SYSTRACE_CALL(); +#if 0 && defined(__ANDROID__) + char scratch[PROP_VALUE_MAX + 1]; + int length = __system_property_get("debug.filament.protected", scratch); + if (swapChain && length > 0) { + uint64_t flags = swapChain->getFlags(); + bool value = bool(atoi(scratch)); + if (value) { + flags |= SwapChain::CONFIG_PROTECTED_CONTENT; + } else { + flags &= ~SwapChain::CONFIG_PROTECTED_CONTENT; + } + swapChain->recreateWithNewFlags(mEngine, flags); + } +#endif + // get the timestamp as soon as possible using namespace std::chrono; const steady_clock::time_point now{ steady_clock::now() }; @@ -365,19 +423,19 @@ void FRenderer::copyFrame(FSwapChain* dstSwapChain, filament::Viewport const& ds params.viewport.bottom = 0; params.viewport.width = std::numeric_limits::max(); params.viewport.height = std::numeric_limits::max(); + driver.beginRenderPass(mRenderTargetHandle, params); + driver.endRenderPass(); } - driver.beginRenderPass(mRenderTargetHandle, params); // Verify that the source swap chain is readable. assert_invariant(mSwapChain->isReadable()); - driver.blit(TargetBufferFlags::COLOR, - mRenderTargetHandle, dstViewport, mRenderTargetHandle, srcViewport, SamplerMagFilter::LINEAR); + driver.blitDEPRECATED(TargetBufferFlags::COLOR, mRenderTargetHandle, + dstViewport, mRenderTargetHandle, srcViewport, SamplerMagFilter::LINEAR); + if (flags & SET_PRESENTATION_TIME) { // TODO: Implement this properly, see https://github.com/google/filament/issues/633 } - driver.endRenderPass(); - if (flags & COMMIT) { dstSwapChain->commit(driver); } @@ -426,7 +484,7 @@ void FRenderer::render(FView const* view) { if (UTILS_LIKELY(view && view->getScene())) { if (mViewRenderedCount) { - // this is a good place to kick the GPU, since we've rendered a View before, + // This is a good place to kick the GPU, since we've rendered a View before, // and we're about to render another one. mEngine.getDriverApi().flush(); } @@ -436,17 +494,17 @@ void FRenderer::render(FView const* view) { } void FRenderer::renderInternal(FView const* view) { - // per-renderpass data - ArenaScope rootArena(mPerRenderPassArena); - FEngine& engine = mEngine; - JobSystem& js = engine.getJobSystem(); + + // per-renderpass data + RootArenaScope rootArenaScope(engine.getPerRenderPassArena()); // create a root job so no other job can escape + JobSystem& js = engine.getJobSystem(); auto *rootJob = js.setRootJob(js.createJob()); // execute the render pass - renderJob(rootArena, const_cast(*view)); + renderJob(rootArenaScope, const_cast(*view)); // make sure to flush the command buffer engine.flush(); @@ -455,7 +513,7 @@ void FRenderer::renderInternal(FView const* view) { js.runAndWait(rootJob); } -void FRenderer::renderJob(ArenaScope& arena, FView& view) { +void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { FEngine& engine = mEngine; JobSystem& js = engine.getJobSystem(); FEngine::DriverApi& driver = engine.getDriverApi(); @@ -464,7 +522,7 @@ void FRenderer::renderJob(ArenaScope& arena, 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; @@ -480,7 +538,16 @@ void FRenderer::renderJob(ArenaScope& arena, 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; @@ -491,6 +558,19 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { hasDithering = false; hasFXAA = false; scale = 1.0f; + } else { + // This configures post-process materials by setting constant parameters + if (taaOptions.enabled) { + ppm.configureTemporalAntiAliasingMaterial(taaOptions); + if (taaOptions.upscaling) { + // for now TAA upscaling is incompatible with regular dsr + dsrOptions.enabled = false; + // also, upscaling doesn't work well with quater-resolution SSAO + aoOptions.resolution = 1.0; + // Currently we only support a fixed TAA upscaling ratio + scale = 0.5f; + } + } } const bool blendModeTranslucent = view.getBlendMode() == BlendMode::TRANSLUCENT; @@ -499,6 +579,8 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { const bool needsAlphaChannel = (mSwapChain && mSwapChain->isTransparent()) || blendModeTranslucent; + const bool isProtectedContent = mSwapChain && mSwapChain->isProtected(); + // asSubpass is disabled with TAA (although it's supported) because performance was degraded // on qualcomm hardware -- we might need a backend dependent toggle at some point const PostProcessManager::ColorGradingConfig colorGradingConfig{ @@ -520,7 +602,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { }; // whether we're scaled at all - const bool scaled = any(notEqual(scale, float2(1.0f))); + bool scaled = any(notEqual(scale, float2(1.0f))); // vp is the user defined viewport within the View filament::Viewport const& vp = view.getViewport(); @@ -607,9 +689,9 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { xvp.bottom = int32_t(guardBand); } - view.prepare(engine, driver, arena, svp, cameraInfo, getShaderUserTime(), needsAlphaChannel); + view.prepare(engine, driver, rootArenaScope, svp, cameraInfo, getShaderUserTime(), needsAlphaChannel); - view.prepareUpscaler(scale); + view.prepareUpscaler(scale, taaOptions, dsrOptions); /* * Allocate command buffer @@ -620,30 +702,36 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // Allocate some space for our commands in the per-frame Arena, and use that space as // an Arena for commands. All this space is released when we exit this method. size_t const perFrameCommandsSize = engine.getPerFrameCommandsSize(); - void* const arenaBegin = arena.allocate(perFrameCommandsSize, CACHELINE_SIZE); + void* const arenaBegin = rootArenaScope.allocate(perFrameCommandsSize, CACHELINE_SIZE); void* const arenaEnd = pointermath::add(arenaBegin, perFrameCommandsSize); + + // This arena *must* stay valid until all commands have been processed RenderPass::Arena commandArena("Command Arena", { arenaBegin, arenaEnd }); RenderPass::RenderFlags renderFlags = 0; if (view.hasShadowing()) renderFlags |= RenderPass::HAS_SHADOWING; if (view.isFrontFaceWindingInverted()) renderFlags |= RenderPass::HAS_INVERSE_FRONT_FACES; - if (view.hasInstancedStereo()) renderFlags |= RenderPass::IS_STEREOSCOPIC; + if (view.hasStereo() && + engine.getConfig().stereoscopicType == backend::StereoscopicType::INSTANCED) { + renderFlags |= RenderPass::IS_INSTANCED_STEREOSCOPIC; + } - RenderPass pass(engine, commandArena); - pass.setRenderFlags(renderFlags); + RenderPassBuilder passBuilder(commandArena); + passBuilder.renderFlags(renderFlags); Variant variant; variant.setDirectionalLighting(view.hasDirectionalLight()); variant.setDynamicLighting(view.hasDynamicLighting()); variant.setFog(view.hasFog()); variant.setVsm(view.hasShadowing() && view.getShadowType() != ShadowType::PCF); - variant.setStereo(view.hasInstancedStereo()); + variant.setStereo(view.hasStereo()); /* * Frame graph */ - FrameGraph fg(engine.getResourceAllocator()); + FrameGraph fg(engine.getResourceAllocator(), + isProtectedContent ? FrameGraph::Mode::PROTECTED : FrameGraph::Mode::UNPROTECTED); auto& blackboard = fg.getBlackboard(); /* @@ -653,10 +741,10 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { if (view.needsShadowMap()) { Variant shadowVariant(Variant::DEPTH_VARIANT); shadowVariant.setVsm(view.getShadowType() == ShadowType::VSM); - - RenderPass shadowPass(pass); - shadowPass.setVariant(shadowVariant); - auto shadows = view.renderShadowMaps(engine, fg, cameraInfo, mShaderUserTime, shadowPass); + auto shadows = view.renderShadowMaps(engine, fg, cameraInfo, mShaderUserTime, + RenderPassBuilder{ commandArena } + .renderFlags(renderFlags) + .variant(shadowVariant)); blackboard["shadows"] = shadows; } @@ -742,8 +830,9 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { view.updatePrimitivesLod(engine, cameraInfo, scene.getRenderableData(), view.getVisibleRenderables()); - pass.setCamera(cameraInfo); - pass.setGeometry(scene.getRenderableData(), view.getVisibleRenderables(), scene.getRenderableUBO()); + passBuilder.camera(cameraInfo); + passBuilder.geometry(scene.getRenderableData(), + view.getVisibleRenderables(), scene.getRenderableUBO()); // view set-ups that need to happen before rendering fg.addTrivialSideEffectPass("Prepare View Uniforms", @@ -789,7 +878,8 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // This is normally used by SSAO and contact-shadows // TODO: the scaling should depends on all passes that need the structure pass - const auto [structure, picking_] = ppm.structure(fg, pass, renderFlags, svp.width, svp.height, { + const auto [structure, picking_] = ppm.structure(fg, + passBuilder, renderFlags, svp.width, svp.height, { .scale = aoOptions.resolution, .picking = view.hasPicking() }); @@ -813,14 +903,14 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { [=, &view](FrameGraphResources const& resources, auto const&, DriverApi& driver) mutable { auto out = resources.getRenderPassInfo(); - view.executePickingQueries(driver, out.target, aoOptions.resolution); + view.executePickingQueries(driver, out.target, scale * aoOptions.resolution); }); } // Store this frame's camera projection in the frame history. if (UTILS_UNLIKELY(taaOptions.enabled)) { // Apply the TAA jitter to everything after the structure pass, starting with the color pass. - ppm.prepareTaa(fg, svp, view.getFrameHistory(), &FrameHistoryEntry::taa, + ppm.prepareTaa(fg, svp, taaOptions, view.getFrameHistory(), &FrameHistoryEntry::taa, &cameraInfo, view.getPerViewUniforms()); } @@ -847,7 +937,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // screen-space reflections pass if (ssReflectionsOptions.enabled) { - auto reflections = ppm.ssr(fg, pass, + auto reflections = ppm.ssr(fg, passBuilder, view.getFrameHistory(), cameraInfo, view.getPerViewUniforms(), structure, @@ -859,15 +949,21 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { PostProcessManager::generateMipmapSSR(ppm, fg, reflections, ssrConfig.reflection, false, ssrConfig); } + config.screenSpaceReflectionHistoryNotReady = !reflections; } // -------------------------------------------------------------------------------------------- // Color passes + // this makes the viewport relative to xvp + // FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to + // know at this point. This will usually be the case when post-process is disabled. + // FIXME: we probably should take the dynamic scaling into account too + passBuilder.scissorViewport(hasPostProcess ? xvp : vp); + // This one doesn't need to be a FrameGraph pass because it always happens by construction // (i.e. it won't be culled, unless everything is culled), so no need to complexify things. - pass.setVariant(variant); - pass.appendCommands(engine, RenderPass::COLOR); + passBuilder.variant(variant); // color-grading as subpass is done either by the color pass or the TAA pass if any auto colorGradingConfigForColor = colorGradingConfig; @@ -875,7 +971,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { if (colorGradingConfigForColor.asSubpass) { // append color grading subpass after all other passes - pass.appendCustomCommand(3, + passBuilder.customCommand(engine, 3, RenderPass::Pass::BLENDED, RenderPass::CustomCommand::EPILOG, 0, [&ppm, &driver, colorGradingConfigForColor]() { @@ -883,7 +979,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { }); } else if (colorGradingConfig.customResolve) { // append custom resolve subpass after all other passes - pass.appendCustomCommand(3, + passBuilder.customCommand(engine, 3, RenderPass::Pass::BLENDED, RenderPass::CustomCommand::EPILOG, 0, [&ppm, &driver]() { @@ -891,23 +987,22 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { }); } - // sort commands once we're done adding commands - pass.sortCommands(engine); - - - // this makes the viewport relative to xvp - // FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to - // know at this point. This will usually be the case when post-process is disabled. - // FIXME: we probably should take the dynamic scaling into account too - pass.setScissorViewport(hasPostProcess ? xvp : vp); + passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::COLOR); + 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) { @@ -915,7 +1010,7 @@ void FRenderer::renderJob(ArenaScope& arena, 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); @@ -933,7 +1028,7 @@ void FRenderer::renderJob(ArenaScope& arena, 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. @@ -971,8 +1066,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { auto& history = view.getFrameHistory(); auto& current = history.getCurrent(); current.ssr.projection = projection; - resources.detach(data.history, - ¤t.ssr.color, ¤t.ssr.desc); + resources.detach(data.history, ¤t.ssr.color, ¤t.ssr.desc); }); } @@ -983,10 +1077,12 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { view.commitUniforms(driver); }); - // resolve depth -- which might be needed because of TAA or DoF. This pass will be culled - // if the depth is not used below. - auto const depth = ppm.resolveBaseLevel(fg, "Resolved Depth Buffer", - blackboard.get("depth")); + // Resolve depth -- which might be needed because of TAA or DoF. This pass will be culled + // if the depth is not used below or if the depth is not MS (e.g. it could have been + // auto-resolved). + // In practice, this is used on Vulkan and older Metal devices. + auto depth = blackboard.get("depth"); + depth = ppm.resolve(fg, "Resolved Depth Buffer", depth, { .levels = 1 }); // Debug: CSM visualisation if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades && @@ -1003,6 +1099,15 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { if (taaOptions.enabled) { input = ppm.taa(fg, input, depth, view.getFrameHistory(), &FrameHistoryEntry::taa, taaOptions, colorGradingConfig); + if (taaOptions.upscaling) { + scale = 1.0f; + scaled = false; + UTILS_UNUSED_IN_RELEASE auto const& inputDesc = fg.getDescriptor(input); + svp.width = inputDesc.width; + svp.height = inputDesc.height; + xvp.width *= 2; + xvp.height *= 2; + } } // -------------------------------------------------------------------------------------------- @@ -1018,16 +1123,21 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // The bokeh height is always correct regardless of the dynamic resolution scaling. // (because the CoC is calculated w.r.t. the height), so we only need to adjust // the width. - float const bokehAspectRatio = scale.x / scale.y; + float const aspect = (scale.x / scale.y) * dofOptions.cocAspectRatio; + float2 const bokehScale{ + aspect < 1.0f ? aspect : 1.0f, + aspect > 1.0f ? 1.0f / aspect : 1.0f + }; input = ppm.dof(fg, input, depth, cameraInfo, needsAlphaChannel, - bokehAspectRatio, dofOptions); + bokehScale, dofOptions); } FrameGraphId bloom, flare; if (bloomOptions.enabled) { - // generate the bloom buffer, which is stored in the blackboard as "bloom". This is + // Generate the bloom buffer, which is stored in the blackboard as "bloom". This is // consumed by the colorGrading pass and will be culled if colorGrading is disabled. - auto [bloom_, flare_] = ppm.bloom(fg, input, bloomOptions, TextureFormat::R11F_G11F_B10F, scale); + auto [bloom_, flare_] = ppm.bloom(fg, input, TextureFormat::R11F_G11F_B10F, + bloomOptions, taaOptions, scale); bloom = bloom_; flare = flare_; } @@ -1056,12 +1166,20 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { auto viewport = DEBUG_DYNAMIC_SCALING ? xvp : vp; input = ppm.upscale(fg, needsAlphaChannel, dsrOptions, input, xvp, { .width = viewport.width, .height = viewport.height, - .format = colorGradingConfig.ldrFormat }); + .format = colorGradingConfig.ldrFormat }, SamplerMagFilter::LINEAR); xvp.left = xvp.bottom = 0; svp = xvp; } } + // 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. @@ -1079,7 +1197,8 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // TODO: in that specific scenario it would be better to just not use xvp // The intermediate buffer is accomplished with a "fake" opaqueBlit (i.e. blit) operation. - const bool outputIsSwapChain = (input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle); + const bool outputIsSwapChain = + (input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle); if (mightNeedFinalBlit) { if (blendModeTranslucent || xvp != svp || @@ -1090,8 +1209,9 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { ssReflectionsOptions.enabled))) { assert_invariant(!scaled); input = ppm.blit(fg, blendModeTranslucent, input, xvp, { - .width = vp.width, .height = vp.height, - .format = colorGradingConfig.ldrFormat }, SamplerMagFilter::NEAREST); + .width = vp.width, .height = vp.height, + .format = colorGradingConfig.ldrFormat }, + SamplerMagFilter::NEAREST, SamplerMinFilter::NEAREST); } } @@ -1102,6 +1222,16 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { "SwapChain::CONFIG_HAS_STENCIL_BUFFER flag set."); } + if (UTILS_UNLIKELY(engine.debug.shadowmap.display_shadow_texture)) { + auto shadowmap = blackboard.get("shadowmap"); + input = ppm.debugDisplayShadowTexture(fg, input, shadowmap, + engine.debug.shadowmap.display_shadow_texture_scale, + engine.debug.shadowmap.display_shadow_texture_layer, + engine.debug.shadowmap.display_shadow_texture_level, + engine.debug.shadowmap.display_shadow_texture_channel, + engine.debug.shadowmap.display_shadow_texture_power); + } + // auto debug = structure // fg.forwardResource(fgViewRenderTarget, debug ? debug : input); diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index 2d08b6cfe0d..056d5599770 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -163,7 +163,7 @@ class FRenderer : public Renderer { } void renderInternal(FView const* view); - void renderJob(ArenaScope& arena, FView& view); + void renderJob(RootArenaScope& rootArenaScope, FView& view); // keep a reference to our engine FEngine& mEngine; @@ -187,9 +187,6 @@ class FRenderer : public Renderer { backend::TargetBufferFlags mClearFlags{}; tsl::robin_set mPreviousRenderTargets; std::function mBeginFrameInternal; - - // per-frame arena for this Renderer - LinearAllocatorArena& mPerRenderPassArena; }; FILAMENT_DOWNCAST(Renderer) diff --git a/filament/src/details/Scene.cpp b/filament/src/details/Scene.cpp index 2d2a99746b3..ff6af6293a5 100644 --- a/filament/src/details/Scene.cpp +++ b/filament/src/details/Scene.cpp @@ -53,7 +53,7 @@ FScene::~FScene() noexcept = default; void FScene::prepare(utils::JobSystem& js, - LinearAllocatorArena& allocator, + RootArenaScope& rootArenaScope, mat4 const& worldTransform, bool shadowReceiversAreCasters) noexcept { // TODO: can we skip this in most cases? Since we rely on indices staying the same, @@ -64,7 +64,7 @@ void FScene::prepare(utils::JobSystem& js, SYSTRACE_CONTEXT(); // This will reset the allocator upon exiting - ArenaScope const arena(allocator); + ArenaScope localArenaScope(rootArenaScope.getArena()); FEngine& engine = mEngine; EntityManager const& em = engine.getEntityManager(); @@ -85,10 +85,10 @@ void FScene::prepare(utils::JobSystem& js, utils::STLAllocator< LightContainerData, LinearAllocatorArena >, false>; RenderableInstanceContainer renderableInstances{ - RenderableInstanceContainer::with_capacity(entities.size(), allocator) }; + RenderableInstanceContainer::with_capacity(entities.size(), localArenaScope.getArena()) }; LightInstanceContainer lightInstances{ - LightInstanceContainer::with_capacity(entities.size(), allocator) }; + LightInstanceContainer::with_capacity(entities.size(), localArenaScope.getArena()) }; SYSTRACE_NAME_BEGIN("InstanceLoop"); @@ -148,7 +148,7 @@ void FScene::prepare(utils::JobSystem& js, // TODO: the resize below could happen in a job - if (sceneData.size() != renderableInstances.size()) { + if (!sceneData.capacity() || sceneData.size() != renderableInstances.size()) { sceneData.clear(); if (sceneData.capacity() < renderableDataCapacity) { sceneData.setCapacity(renderableDataCapacity); @@ -267,16 +267,16 @@ void FScene::prepare(utils::JobSystem& js, // in the code below, we only transform directions, so the translation of the // world transform is irrelevant, and we don't need to use getWorldTransformAccurate() + mat3 const worldDirectionTransform = + mat3::getTransformForNormals(tcm.getWorldTransformAccurate(ti).upperLeft()); FLightManager::ShadowParams const params = lcm.getShadowParams(li); - float3 const localDirection = lcm.getLocalDirection(li); - float3 const shadowLocalDirection = params.options.transform * localDirection; - mat3 const worldDirectionTransform = tcm.getWorldTransformAccurate(ti).upperLeft(); - mat3 const shaderWorldTransform = worldTransform.upperLeft() * worldDirectionTransform; + float3 const localDirection = worldDirectionTransform * lcm.getLocalDirection(li); + double3 const shadowLocalDirection = params.options.transform * localDirection; // using mat3::getTransformForNormals handles non-uniform scaling // note: in the common case of the rigid-body transform, getTransformForNormals() returns // identity. - mat3 const worlTransformNormals = mat3::getTransformForNormals(shaderWorldTransform); + mat3 const worlTransformNormals = mat3::getTransformForNormals(worldTransform.upperLeft()); double3 const d = worlTransformNormals * localDirection; double3 const s = worlTransformNormals * shadowLocalDirection; @@ -290,10 +290,8 @@ void FScene::prepare(utils::JobSystem& js, // is pointing down, which is a common case for lights. See ShadowMap.cpp. return transpose(mat3::lookTo(direction, double3{ 1, 0, 0 })); }; - double3 const worldDirection = - mat3::getTransformForNormals(worldDirectionTransform) * shadowLocalDirection; double3 const worldOrigin = transpose(worldTransform.upperLeft()) * worldTransform[3].xyz; - mat3 const Mv = getMv(worldDirection); + mat3 const Mv = getMv(shadowLocalDirection); double2 const lsReferencePoint = (Mv * worldOrigin).xy; constexpr float inf = std::numeric_limits::infinity(); @@ -456,7 +454,7 @@ void FScene::terminate(FEngine&) { mRenderableViewUbh.clear(); } -void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope&, +void FScene::prepareDynamicLights(const CameraInfo& camera, Handle lightUbh) noexcept { FEngine::DriverApi& driver = mEngine.getDriverApi(); FLightManager const& lcm = mEngine.getLightManager(); diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index 2e0aff2ae8b..490d115af3c 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -31,6 +31,8 @@ #include #include +#include + #include #include #include @@ -55,7 +57,7 @@ class FSkybox; class FScene : public Scene { public: /* - * Filaments-scope Public API + * Filament-scope Public API */ FSkybox* getSkybox() const noexcept { return mSkybox; } @@ -70,12 +72,12 @@ class FScene : public Scene { ~FScene() noexcept; void terminate(FEngine& engine); - void prepare(utils::JobSystem& js, LinearAllocatorArena& allocator, + void prepare(utils::JobSystem& js, RootArenaScope& rootArenaScope, math::mat4 const& worldTransform, bool shadowReceiversAreCasters) noexcept; void prepareVisibleRenderables(utils::Range visibleRenderables) noexcept; - void prepareDynamicLights(const CameraInfo& camera, ArenaScope& arena, + void prepareDynamicLights(const CameraInfo& camera, backend::Handle lightUbh) noexcept; backend::Handle getRenderableUBO() const noexcept { @@ -197,6 +199,7 @@ class FScene : public Scene { void addEntities(const utils::Entity* entities, size_t count); void remove(utils::Entity entity); void removeEntities(const utils::Entity* entities, size_t count); + size_t getEntityCount() const noexcept { return mEntities.size(); } size_t getRenderableCount() const noexcept; size_t getLightCount() const noexcept; bool hasEntity(utils::Entity entity) const noexcept; diff --git a/filament/src/details/SkinningBuffer.cpp b/filament/src/details/SkinningBuffer.cpp index b70e454af5e..74de88f713b 100644 --- a/filament/src/details/SkinningBuffer.cpp +++ b/filament/src/details/SkinningBuffer.cpp @@ -242,7 +242,8 @@ FSkinningBuffer::HandleIndicesAndWeights FSkinningBuffer::createIndicesAndWeight getSkinningBufferWidth(count), getSkinningBufferHeight(count), 1, TextureUsage::DEFAULT); - samplerHandle = driver.createSamplerGroup(PerRenderPrimitiveSkinningSib::SAMPLER_COUNT); + samplerHandle = driver.createSamplerGroup(PerRenderPrimitiveSkinningSib::SAMPLER_COUNT, + utils::FixedSizeString<32>("Skinning buffer samplers")); SamplerGroup samplerGroup(PerRenderPrimitiveSkinningSib::SAMPLER_COUNT); samplerGroup.setSampler(PerRenderPrimitiveSkinningSib::BONE_INDICES_AND_WEIGHTS, { textureHandle, {}}); diff --git a/filament/src/details/Skybox.cpp b/filament/src/details/Skybox.cpp index 969eccdb455..39aca8ef8ca 100644 --- a/filament/src/details/Skybox.cpp +++ b/filament/src/details/Skybox.cpp @@ -118,13 +118,24 @@ FSkybox::FSkybox(FEngine& engine, const Builder& builder) noexcept FMaterial const* FSkybox::createMaterial(FEngine& engine) { Material::Builder builder; -#ifdef FILAMENT_TARGET_MOBILE +#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/details/Stream.cpp b/filament/src/details/Stream.cpp index 71945cd7eb9..317113ace03 100644 --- a/filament/src/details/Stream.cpp +++ b/filament/src/details/Stream.cpp @@ -86,11 +86,13 @@ void FStream::terminate(FEngine& engine) noexcept { engine.getDriverApi().destroyStream(mStreamHandle); } -void FStream::setAcquiredImage(void* image, Callback callback, void* userdata) noexcept { +void FStream::setAcquiredImage(void* image, + Callback callback, void* userdata) noexcept { mEngine.getDriverApi().setAcquiredImage(mStreamHandle, image, nullptr, callback, userdata); } -void FStream::setAcquiredImage(void* image, CallbackHandler* handler, Callback callback, void* userdata) noexcept { +void FStream::setAcquiredImage(void* image, + CallbackHandler* handler, Callback callback, void* userdata) noexcept { mEngine.getDriverApi().setAcquiredImage(mStreamHandle, image, handler, callback, userdata); } diff --git a/filament/src/details/SwapChain.cpp b/filament/src/details/SwapChain.cpp index d9cb80911d9..ef4eb4fabd4 100644 --- a/filament/src/details/SwapChain.cpp +++ b/filament/src/details/SwapChain.cpp @@ -18,24 +18,64 @@ #include "details/Engine.h" +#include + +#include + +#include + +#include +#include + +#include + namespace filament { FSwapChain::FSwapChain(FEngine& engine, void* nativeWindow, uint64_t flags) - : mEngine(engine), mNativeWindow(nativeWindow), mConfigFlags(flags) { - mSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, flags); + : mEngine(engine), mNativeWindow(nativeWindow), mConfigFlags(initFlags(engine, flags)) { + mHwSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, flags); } FSwapChain::FSwapChain(FEngine& engine, uint32_t width, uint32_t height, uint64_t flags) - : mEngine(engine), mConfigFlags(flags) { - mSwapChain = engine.getDriverApi().createSwapChainHeadless(width, height, flags); + : mEngine(engine), mWidth(width), mHeight(height), mConfigFlags(initFlags(engine, flags)) { + mHwSwapChain = engine.getDriverApi().createSwapChainHeadless(width, height, flags); +} + +void FSwapChain::recreateWithNewFlags(FEngine& engine, uint64_t flags) noexcept { + flags = initFlags(engine, flags); + if (flags != mConfigFlags) { + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.destroySwapChain(mHwSwapChain); + mConfigFlags = flags; + if (mNativeWindow) { + mHwSwapChain = driver.createSwapChain(mNativeWindow, flags); + } else { + mHwSwapChain = driver.createSwapChainHeadless(mWidth, mHeight, flags); + } + } +} + +uint64_t FSwapChain::initFlags(FEngine& engine, uint64_t flags) noexcept { + if (!isSRGBSwapChainSupported(engine)) { + flags &= ~CONFIG_SRGB_COLORSPACE; + } + if (!isProtectedContentSupported(engine)) { + flags &= ~CONFIG_PROTECTED_CONTENT; + } + return flags; } void FSwapChain::terminate(FEngine& engine) noexcept { - engine.getDriverApi().destroySwapChain(mSwapChain); + engine.getDriverApi().destroySwapChain(mHwSwapChain); } void FSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) { - mEngine.getDriverApi().setFrameScheduledCallback(mSwapChain, callback, user); + mFrameScheduledCallback = callback; + mEngine.getDriverApi().setFrameScheduledCallback(mHwSwapChain, callback, user); +} + +SwapChain::FrameScheduledCallback FSwapChain::getFrameScheduledCallback() const noexcept { + return mFrameScheduledCallback; } void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler, @@ -52,9 +92,9 @@ void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler, if (callback) { auto* const user = new(std::nothrow) Callback{ std::move(callback), this }; mEngine.getDriverApi().setFrameCompletedCallback( - mSwapChain, handler, &Callback::func, static_cast(user)); + mHwSwapChain, handler, &Callback::func, static_cast(user)); } else { - mEngine.getDriverApi().setFrameCompletedCallback(mSwapChain, nullptr, nullptr, nullptr); + mEngine.getDriverApi().setFrameCompletedCallback(mHwSwapChain, nullptr, nullptr, nullptr); } } @@ -62,4 +102,8 @@ bool FSwapChain::isSRGBSwapChainSupported(FEngine& engine) noexcept { return engine.getDriverApi().isSRGBSwapChainSupported(); } +bool FSwapChain::isProtectedContentSupported(FEngine& engine) noexcept { + return engine.getDriverApi().isProtectedContentSupported(); +} + } // namespace filament diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h index 3c820b9799f..7a97727e832 100644 --- a/filament/src/details/SwapChain.h +++ b/filament/src/details/SwapChain.h @@ -24,9 +24,12 @@ #include #include +#include +#include #include -#include + +#include namespace filament { @@ -39,45 +42,66 @@ class FSwapChain : public SwapChain { void terminate(FEngine& engine) noexcept; void makeCurrent(backend::DriverApi& driverApi) noexcept { - driverApi.makeCurrent(mSwapChain, mSwapChain); + driverApi.makeCurrent(mHwSwapChain, mHwSwapChain); } void commit(backend::DriverApi& driverApi) noexcept { - driverApi.commit(mSwapChain); + driverApi.commit(mHwSwapChain); } void* getNativeWindow() const noexcept { return mNativeWindow; } - constexpr bool isTransparent() const noexcept { + bool isTransparent() const noexcept { return (mConfigFlags & CONFIG_TRANSPARENT) != 0; } - constexpr bool isReadable() const noexcept { + bool isReadable() const noexcept { return (mConfigFlags & CONFIG_READABLE) != 0; } - constexpr bool hasStencilBuffer() const noexcept { + bool hasStencilBuffer() const noexcept { return (mConfigFlags & CONFIG_HAS_STENCIL_BUFFER) != 0; } + bool isProtected() const noexcept { + return (mConfigFlags & CONFIG_PROTECTED_CONTENT) != 0; + } + + // This returns the effective flags. Unsupported flags are cleared automatically. + uint64_t getFlags() const noexcept { + return mConfigFlags; + } + backend::Handle getHwHandle() const noexcept { - return mSwapChain; + return mHwSwapChain; } void setFrameScheduledCallback(FrameScheduledCallback callback, void* user); + FrameScheduledCallback getFrameScheduledCallback() const noexcept; + void setFrameCompletedCallback(backend::CallbackHandler* handler, utils::Invocable&& callback) noexcept; static bool isSRGBSwapChainSupported(FEngine& engine) noexcept; + static bool isProtectedContentSupported(FEngine& engine) noexcept; + + // This is currently only used for debugging. This allows to recreate the HwSwapChain with + // new flags. + void recreateWithNewFlags(FEngine& engine, uint64_t flags) noexcept; + private: FEngine& mEngine; - backend::Handle mSwapChain; - void* mNativeWindow = nullptr; - uint64_t mConfigFlags = 0; + backend::Handle mHwSwapChain; + FrameScheduledCallback mFrameScheduledCallback{}; + void* mNativeWindow{}; + uint32_t mWidth{}; + uint32_t mHeight{}; + uint64_t mConfigFlags{}; + static uint64_t initFlags(FEngine& engine, uint64_t flags) noexcept; }; FILAMENT_DOWNCAST(SwapChain) diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index 92a3b32808c..e47f7251b23 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -25,14 +25,33 @@ #include +#include +#include + #include #include #include #include +#include +#include +#include + +#include +#include +#include +#include #include #include +#include +#include +#include +#include + +#include +#include + using namespace utils; namespace filament { @@ -58,7 +77,7 @@ struct Texture::BuilderDetails { uint8_t mLevels = 1; Sampler mTarget = Sampler::SAMPLER_2D; InternalFormat mFormat = InternalFormat::RGBA8; - Usage mUsage = Usage::DEFAULT; + Usage mUsage = Usage::NONE; bool mTextureIsSwizzled = false; std::array mSwizzle = { Swizzle::CHANNEL_0, Swizzle::CHANNEL_1, @@ -125,6 +144,43 @@ Texture* Texture::Builder::build(Engine& engine) { ASSERT_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat), "Texture format %u not supported on this platform", mImpl->mFormat); + const bool isProtectedTexturesSupported = + downcast(engine).getDriverApi().isProtectedTexturesSupported(); + const bool useProtectedMemory = bool(mImpl->mUsage & TextureUsage::PROTECTED); + + ASSERT_PRECONDITION((isProtectedTexturesSupported && useProtectedMemory) || !useProtectedMemory, + "Texture is PROTECTED but protected textures are not supported"); + + uint8_t maxLevelCount; + switch (mImpl->mTarget) { + case SamplerType::SAMPLER_2D: + case SamplerType::SAMPLER_2D_ARRAY: + case SamplerType::SAMPLER_CUBEMAP: + case SamplerType::SAMPLER_EXTERNAL: + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + maxLevelCount = FTexture::maxLevelCount(mImpl->mWidth, mImpl->mHeight); + break; + case SamplerType::SAMPLER_3D: + maxLevelCount = FTexture::maxLevelCount(std::max( + { mImpl->mWidth, mImpl->mHeight, mImpl->mDepth })); + break; + } + mImpl->mLevels = std::min(mImpl->mLevels, maxLevelCount); + + if (mImpl->mUsage == TextureUsage::NONE) { + mImpl->mUsage = TextureUsage::DEFAULT; + if (mImpl->mLevels > 1 && + (mImpl->mWidth > 1 || mImpl->mHeight > 1) && + mImpl->mTarget != SamplerType::SAMPLER_EXTERNAL) { + const bool formatMipmappable = + downcast(engine).getDriverApi().isTextureFormatMipmappable(mImpl->mFormat); + if (formatMipmappable) { + // by default mipmappable textures have the BLIT usage bits set + mImpl->mUsage |= TextureUsage::BLIT_SRC | TextureUsage::BLIT_DST; + } + } + } + const bool sampleable = bool(mImpl->mUsage & TextureUsage::SAMPLEABLE); const bool swizzled = mImpl->mTextureIsSwizzled; const bool imported = mImpl->mImportedId; @@ -163,30 +219,15 @@ Texture* Texture::Builder::build(Engine& engine) { // ------------------------------------------------------------------------------------------------ FTexture::FTexture(FEngine& engine, const Builder& builder) { + FEngine::DriverApi& driver = engine.getDriverApi(); mWidth = static_cast(builder->mWidth); mHeight = static_cast(builder->mHeight); mDepth = static_cast(builder->mDepth); mFormat = builder->mFormat; mUsage = builder->mUsage; mTarget = builder->mTarget; + mLevelCount = builder->mLevels; - uint8_t maxLevelCount; - switch (builder->mTarget) { - case SamplerType::SAMPLER_2D: - case SamplerType::SAMPLER_2D_ARRAY: - case SamplerType::SAMPLER_CUBEMAP: - case SamplerType::SAMPLER_EXTERNAL: - case SamplerType::SAMPLER_CUBEMAP_ARRAY: - maxLevelCount = FTexture::maxLevelCount(mWidth, mHeight); - break; - case SamplerType::SAMPLER_3D: - maxLevelCount = FTexture::maxLevelCount(std::max(mWidth, std::max(mHeight, mDepth))); - break; - } - - mLevelCount = std::min(builder->mLevels, maxLevelCount); - - FEngine::DriverApi& driver = engine.getDriverApi(); if (UTILS_LIKELY(builder->mImportedId == 0)) { if (UTILS_LIKELY(!builder->mTextureIsSwizzled)) { mHandle = driver.createTexture( @@ -224,20 +265,20 @@ size_t FTexture::getDepth(size_t level) const noexcept { void FTexture::setImage(FEngine& engine, size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, - FTexture::PixelBufferDescriptor&& buffer) const { + FTexture::PixelBufferDescriptor&& p) const { if (UTILS_UNLIKELY(!engine.hasFeatureLevel(FeatureLevel::FEATURE_LEVEL_1))) { - ASSERT_PRECONDITION(buffer.stride == 0 || buffer.stride == width, + ASSERT_PRECONDITION(p.stride == 0 || p.stride == width, "PixelBufferDescriptor stride must be 0 (or width) at FEATURE_LEVEL_0"); } // this should have been validated already assert_invariant(isTextureFormatSupported(engine, mFormat)); - ASSERT_PRECONDITION(buffer.type == PixelDataType::COMPRESSED || - validatePixelFormatAndType(mFormat, buffer.format, buffer.type), + ASSERT_PRECONDITION(p.type == PixelDataType::COMPRESSED || + validatePixelFormatAndType(mFormat, p.format, p.type), "The combination of internal format=%u and {format=%u, type=%u} is not supported.", - unsigned(mFormat), unsigned(buffer.format), unsigned(buffer.type)); + unsigned(mFormat), unsigned(p.format), unsigned(p.type)); ASSERT_PRECONDITION(!mStream, "setImage() called on a Stream texture."); @@ -261,7 +302,7 @@ void FTexture::setImage(FEngine& engine, size_t level, unsigned(yoffset), unsigned(height), unsigned(valueForLevel(level, mHeight)), unsigned(level)); - ASSERT_PRECONDITION(buffer.buffer, "Data buffer is nullptr."); + ASSERT_PRECONDITION(p.buffer, "Data buffer is nullptr."); uint32_t effectiveTextureDepthOrLayers; switch (mTarget) { @@ -289,8 +330,21 @@ void FTexture::setImage(FEngine& engine, size_t level, "zoffset (%u) + depth (%u) > texture depth (%u) at level (%u)", unsigned(zoffset), unsigned(depth), effectiveTextureDepthOrLayers, unsigned(level)); + using PBD = PixelBufferDescriptor; + size_t const stride = p.stride ? p.stride : width; + size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1); + size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment); + size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride" + // TODO: PBD should have a p.depth (# layers to skip) + ASSERT_PRECONDITION(bpp * p.left + bpr * p.top + bpl * (0 + depth) <= p.size, + "buffer overflow: (size=%lu, stride=%lu, left=%u, top=%u) smaller than specified region " + "{{%u,%u,%u},{%u,%u,%u)}}", + size_t(p.size), size_t(p.stride), unsigned(p.left), unsigned(p.top), + unsigned(xoffset), unsigned(yoffset), unsigned(zoffset), + unsigned(width), unsigned(height), unsigned(depth)); + engine.getDriverApi().update3DImage(mHandle, - uint8_t(level), xoffset, yoffset, zoffset, width, height, depth, std::move(buffer)); + uint8_t(level), xoffset, yoffset, zoffset, width, height, depth, std::move(p)); } // deprecated @@ -391,6 +445,9 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept { ASSERT_PRECONDITION(mTarget != SamplerType::SAMPLER_EXTERNAL, "External Textures are not mipmappable."); + ASSERT_PRECONDITION(mTarget != SamplerType::SAMPLER_3D, + "3D Textures are not mipmappable."); + const bool formatMipmappable = engine.getDriverApi().isTextureFormatMipmappable(mFormat); ASSERT_PRECONDITION(formatMipmappable, "Texture format %u is not mipmappable.", (unsigned)mFormat); @@ -399,80 +456,17 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept { return; } - if (engine.getDriverApi().canGenerateMipmaps()) { - engine.getDriverApi().generateMipmaps(mHandle); - return; - } - - auto generateMipsForLayer = [this, &engine](TargetBufferInfo proto) { - FEngine::DriverApi& driver = engine.getDriverApi(); - - // Wrap miplevel 0 in a render target so that we can use it as a blit source. - uint8_t level = 0; - uint32_t srcw = mWidth; - uint32_t srch = mHeight; - proto.handle = mHandle; - proto.level = level++; - backend::Handle srcrth = driver.createRenderTarget( - TargetBufferFlags::COLOR, srcw, srch, mSampleCount, proto, {}, {}); - - // Perform a blit for all miplevels down to 1x1. - backend::Handle dstrth; - do { - uint32_t const dstw = std::max(srcw >> 1u, 1u); - uint32_t const dsth = std::max(srch >> 1u, 1u); - proto.level = level++; - dstrth = driver.createRenderTarget( - TargetBufferFlags::COLOR, dstw, dsth, mSampleCount, proto, {}, {}); - driver.blit(TargetBufferFlags::COLOR, - dstrth, { 0, 0, dstw, dsth }, - srcrth, { 0, 0, srcw, srch }, - SamplerMagFilter::LINEAR); - driver.destroyRenderTarget(srcrth); - srcrth = dstrth; - srcw = dstw; - srch = dsth; - } while ((srcw > 1 || srch > 1) && level < mLevelCount); - driver.destroyRenderTarget(dstrth); - }; - - switch (mTarget) { - case SamplerType::SAMPLER_2D: - generateMipsForLayer({}); - break; - case SamplerType::SAMPLER_2D_ARRAY: - UTILS_NOUNROLL - for (uint16_t layer = 0, c = mDepth; layer < c; ++layer) { - generateMipsForLayer({ .layer = layer }); - } - break; - case SamplerType::SAMPLER_CUBEMAP_ARRAY: - UTILS_NOUNROLL - for (uint16_t layer = 0, c = mDepth * 6; layer < c; ++layer) { - generateMipsForLayer({ .layer = layer }); - } - break; - case SamplerType::SAMPLER_CUBEMAP: - UTILS_NOUNROLL - for (uint8_t face = 0; face < 6; ++face) { - generateMipsForLayer({ .layer = face }); - } - break; - case SamplerType::SAMPLER_EXTERNAL: - // not mipmapable - break; - case SamplerType::SAMPLER_3D: - // TODO: handle SAMPLER_3D -- this can't be done with a 2D blit, this would require - // a fragment shader - slog.w << "Texture::generateMipmap does not support SAMPLER_3D yet on this h/w." << io::endl; - break; - } + engine.getDriverApi().generateMipmaps(mHandle); } bool FTexture::isTextureFormatSupported(FEngine& engine, InternalFormat format) noexcept { return engine.getDriverApi().isTextureFormatSupported(format); } +bool FTexture::isProtectedTexturesSupported(FEngine& engine) noexcept { + return engine.getDriverApi().isProtectedTexturesSupported(); +} + bool FTexture::isTextureSwizzleSupported(FEngine& engine) noexcept { return engine.getDriverApi().isTextureSwizzleSupported(); } diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index cbf02e30424..0e0fe9320ee 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -79,10 +79,13 @@ class FTexture : public Texture { * Utilities */ - // synchronous call to the backend. returns whether a backend supports a particular format. + // Synchronous call to the backend. Returns whether a backend supports a particular format. static bool isTextureFormatSupported(FEngine& engine, InternalFormat format) noexcept; - // synchronous call to the backend. returns whether a backend supports texture swizzling. + // Synchronous call to the backend. Returns whether a backend supports protected textures. + static bool isProtectedTexturesSupported(FEngine& engine) noexcept; + + // Synchronous call to the backend. Returns whether a backend supports texture swizzling. static bool isTextureSwizzleSupported(FEngine& engine) noexcept; // storage needed on the CPU side for texture data uploads @@ -104,7 +107,7 @@ class FTexture : public Texture { // Returns the max number of levels for a texture of given dimensions static inline uint8_t maxLevelCount(uint32_t width, uint32_t height) noexcept { - uint32_t maxDimension = std::max(width, height); + uint32_t const maxDimension = std::max(width, height); return maxLevelCount(maxDimension); } diff --git a/filament/src/details/VertexBuffer.cpp b/filament/src/details/VertexBuffer.cpp index f557e25d233..d718bbe0a85 100644 --- a/filament/src/details/VertexBuffer.cpp +++ b/filament/src/details/VertexBuffer.cpp @@ -21,18 +21,41 @@ #include "FilamentAPI-impl.h" -#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include + +#include +#include + namespace filament { using namespace backend; using namespace filament::math; struct VertexBuffer::BuilderDetails { - FVertexBuffer::AttributeData mAttributes[MAX_VERTEX_ATTRIBUTE_COUNT]; + struct AttributeData : backend::Attribute { + AttributeData() : backend::Attribute{ .type = backend::ElementType::FLOAT4 } { + static_assert(sizeof(Attribute) == sizeof(AttributeData), + "Attribute and Builder::Attribute must match"); + } + }; + std::array mAttributes{}; AttributeBitset mDeclaredAttributes; uint32_t mVertexCount = 0; uint8_t mBufferCount = 0; @@ -88,11 +111,16 @@ VertexBuffer::Builder& VertexBuffer::Builder::attribute(VertexAttribute attribut } #endif - FVertexBuffer::AttributeData& entry = mImpl->mAttributes[attribute]; + auto& entry = mImpl->mAttributes[attribute]; entry.buffer = bufferIndex; entry.offset = byteOffset; entry.stride = byteStride; entry.type = attributeType; + if (attribute == VertexAttribute::BONE_INDICES) { + // BONE_INDICES must always be an integer type + entry.flags |= Attribute::FLAG_INTEGER_TARGET; + } + mImpl->mDeclaredAttributes.set(attribute); } else { utils::slog.w << "Ignoring VertexBuffer attribute, the limit of " << @@ -104,7 +132,7 @@ VertexBuffer::Builder& VertexBuffer::Builder::attribute(VertexAttribute attribut VertexBuffer::Builder& VertexBuffer::Builder::normalized(VertexAttribute attribute, bool normalized) noexcept { if (size_t(attribute) < MAX_VERTEX_ATTRIBUTE_COUNT) { - FVertexBuffer::AttributeData& entry = mImpl->mAttributes[attribute]; + auto& entry = mImpl->mAttributes[attribute]; if (normalized) { entry.flags |= Attribute::FLAG_NORMALIZED; } else { @@ -130,15 +158,47 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) { auto const& declaredAttributes = mImpl->mDeclaredAttributes; auto const& attributes = mImpl->mAttributes; utils::bitset32 attributedBuffers; - for (size_t j = 0; j < MAX_VERTEX_ATTRIBUTE_COUNT; ++j) { - if (declaredAttributes[j]) { - attributedBuffers.set(attributes[j].buffer); + + declaredAttributes.forEachSetBit([&](size_t j){ + // update set of used buffers + attributedBuffers.set(attributes[j].buffer); + + if (engine.getActiveFeatureLevel() == backend::FeatureLevel::FEATURE_LEVEL_0) { + ASSERT_PRECONDITION(!(attributes[j].flags & Attribute::FLAG_INTEGER_TARGET), + "Attribute::FLAG_INTEGER_TARGET not supported at FEATURE_LEVEL_0"); } - } + + // also checks that we don't use an invalid type with integer attributes + if (attributes[j].flags & Attribute::FLAG_INTEGER_TARGET) { + using ET = ElementType; + constexpr uint32_t const invalidIntegerTypes = + (1 << (int)ET::FLOAT) | + (1 << (int)ET::FLOAT2) | + (1 << (int)ET::FLOAT3) | + (1 << (int)ET::FLOAT4) | + (1 << (int)ET::HALF) | + (1 << (int)ET::HALF2) | + (1 << (int)ET::HALF3) | + (1 << (int)ET::HALF4); + ASSERT_PRECONDITION(!(invalidIntegerTypes & (1 << (int)attributes[j].type)), + "invalid integer vertex attribute type %d", attributes[j].type); + } + }); ASSERT_PRECONDITION(attributedBuffers.count() == mImpl->mBufferCount, "At least one buffer slot was never assigned to an attribute."); + if (mImpl->mAdvancedSkinningEnabled) { + ASSERT_PRECONDITION(!mImpl->mDeclaredAttributes[VertexAttribute::BONE_INDICES], + "Vertex buffer attribute BONE_INDICES is already defined, " + "no advanced skinning is allowed"); + ASSERT_PRECONDITION(!mImpl->mDeclaredAttributes[VertexAttribute::BONE_WEIGHTS], + "Vertex buffer attribute BONE_WEIGHTS is already defined, " + "no advanced skinning is allowed"); + ASSERT_PRECONDITION(mImpl->mBufferCount < (MAX_VERTEX_BUFFER_COUNT - 2), + "Vertex buffer uses to many buffers (%u)", mImpl->mBufferCount); + } + return downcast(engine).createVertexBuffer(*this); } @@ -149,96 +209,106 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& build mBufferObjectsEnabled(builder->mBufferObjectsEnabled), mAdvancedSkinningEnabled(builder->mAdvancedSkinningEnabled){ std::copy(std::begin(builder->mAttributes), std::end(builder->mAttributes), mAttributes.begin()); - mDeclaredAttributes = builder->mDeclaredAttributes; - uint8_t const attributeCount = (uint8_t)mDeclaredAttributes.count(); - - AttributeArray attributeArray; - - static_assert(attributeArray.size() == MAX_VERTEX_ATTRIBUTE_COUNT, - "Attribute and Builder::Attribute arrays must match"); - - static_assert(sizeof(Attribute) == sizeof(AttributeData), - "Attribute and Builder::Attribute must match"); if (mAdvancedSkinningEnabled) { - ASSERT_PRECONDITION(!mDeclaredAttributes[VertexAttribute::BONE_INDICES], - "Vertex buffer attribute BONE_INDICES is already defined, no advanced skinning is allowed"); - ASSERT_PRECONDITION(!mDeclaredAttributes[VertexAttribute::BONE_WEIGHTS], - "Vertex buffer attribute BONE_WEIGHTS is already defined, no advanced skinning is allowed"); - ASSERT_PRECONDITION(mBufferCount < (MAX_VERTEX_BUFFER_COUNT - 2), - "Vertex buffer uses to many buffers (%u)", mBufferCount); + mAttributes[VertexAttribute::BONE_INDICES] = { + .offset = 0, + .stride = 8, + .buffer = mBufferCount, + .type = VertexBuffer::AttributeType::USHORT4, + .flags = Attribute::FLAG_INTEGER_TARGET, + }; mDeclaredAttributes.set(VertexAttribute::BONE_INDICES); - mAttributes[VertexAttribute::BONE_INDICES].offset = 0; - mAttributes[VertexAttribute::BONE_INDICES].stride = 8; - mAttributes[VertexAttribute::BONE_INDICES].buffer = mBufferCount; - mAttributes[VertexAttribute::BONE_INDICES].type = VertexBuffer::AttributeType::USHORT4; - mAttributes[VertexAttribute::BONE_INDICES].flags = Attribute::FLAG_INTEGER_TARGET; mBufferCount++; + + mAttributes[VertexAttribute::BONE_WEIGHTS] = { + .offset = 0, + .stride = 16, + .buffer = mBufferCount, + .type = VertexBuffer::AttributeType::FLOAT4, + .flags = 0, + }; mDeclaredAttributes.set(VertexAttribute::BONE_WEIGHTS); - mAttributes[VertexAttribute::BONE_WEIGHTS].offset = 0; - mAttributes[VertexAttribute::BONE_WEIGHTS].stride = 16; - mAttributes[VertexAttribute::BONE_WEIGHTS].buffer = mBufferCount; - mAttributes[VertexAttribute::BONE_WEIGHTS].type = VertexBuffer::AttributeType::FLOAT4; - mAttributes[VertexAttribute::BONE_WEIGHTS].flags = 0; mBufferCount++; + } else { + // Because the Material's SKN variant supports both skinning and morphing, it expects + // all attributes related to *both* to be present. In turn, this means that a VertexBuffer + // used for skinning and/or morphing, needs to provide all related attributes. + // Currently, the backend must handle disabled arrays in the VertexBuffer that are declared + // in the shader. In GL this happens automatically, in vulkan/metal, the backends have to + // use dummy buffers. + // - A complication is that backends need to know if an attribute is declared as float or + // integer in the shader, regardless of if the attribute is enabled or not in the + // VertexBuffer (e.g. the morphing attributes could be disabled because we're only using + // skinning). + // - Another complication is that the SKN variant is selected by the renderable + // (as opposed to the RenderPrimitive), so it's possible and valid for a primitive + // that isn't skinned nor morphed to be rendered with the SKN variant (morphing/skinning + // will then be disabled dynamically). + // + // Because of that we need to set FLAG_INTEGER_TARGET on all attributes that we know are + // integer in the shader and the bottom line is that BONE_INDICES always needs to be set to + // FLAG_INTEGER_TARGET. + mAttributes[BONE_INDICES].flags |= Attribute::FLAG_INTEGER_TARGET; } - size_t bufferSizes[MAX_VERTEX_BUFFER_COUNT] = {}; + FEngine::DriverApi& driver = engine.getDriverApi(); - auto const& declaredAttributes = mDeclaredAttributes; - auto const& attributes = mAttributes; - #pragma nounroll - for (size_t i = 0, n = attributeArray.size(); i < n; ++i) { - if (declaredAttributes[i]) { - const uint32_t offset = attributes[i].offset; - const uint8_t stride = attributes[i].stride; - const uint8_t slot = attributes[i].buffer; - - attributeArray[i].offset = offset; - attributeArray[i].stride = stride; - attributeArray[i].buffer = slot; - attributeArray[i].type = attributes[i].type; - attributeArray[i].flags = attributes[i].flags; + mVertexBufferInfoHandle = engine.getVertexBufferInfoFactory().create(driver, + mBufferCount, mDeclaredAttributes.count(), mAttributes); + + mHandle = driver.createVertexBuffer(mVertexCount, mVertexBufferInfoHandle); + + // calculate buffer sizes + size_t bufferSizes[MAX_VERTEX_BUFFER_COUNT] = {}; + #pragma nounroll + for (size_t i = 0, n = mAttributes.size(); i < n; ++i) { + if (mDeclaredAttributes[i]) { + const uint32_t offset = mAttributes[i].offset; + const uint8_t stride = mAttributes[i].stride; + const uint8_t slot = mAttributes[i].buffer; const size_t end = offset + mVertexCount * stride; - bufferSizes[slot] = math::max(bufferSizes[slot], end); + if (slot != Attribute::BUFFER_UNUSED) { + assert_invariant(slot < MAX_VERTEX_BUFFER_COUNT); + bufferSizes[slot] = std::max(bufferSizes[slot], end); + } } } - // Backends do not (and should not) know the semantics of each vertex attribute, but they - // need to know whether the vertex shader consumes them as integers or as floats. - // NOTE: This flag needs to be set regardless of whether the attribute is actually declared. - attributeArray[BONE_INDICES].flags |= Attribute::FLAG_INTEGER_TARGET; - - FEngine::DriverApi& driver = engine.getDriverApi(); - - mHandle = driver.createVertexBuffer( - mBufferCount, attributeCount, mVertexCount, attributeArray); - - // If buffer objects are not enabled at the API level, then we create them internally. if (!mBufferObjectsEnabled) { + // If buffer objects are not enabled at the API level, then we create them internally. #pragma nounroll - for (size_t i = 0; i < MAX_VERTEX_BUFFER_COUNT; ++i) { - if (bufferSizes[i] > 0) { - BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], - backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); - driver.setVertexBufferObject(mHandle, i, bo); - mBufferObjects[i] = bo; + for (size_t index = 0; index < MAX_VERTEX_BUFFER_COUNT; ++index) { + size_t const i = mAttributes[index].buffer; + if (i != Attribute::BUFFER_UNUSED) { + assert_invariant(bufferSizes[i] > 0); + if (!mBufferObjects[i]) { + BufferObjectHandle bo = driver.createBufferObject(bufferSizes[i], + backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); + driver.setVertexBufferObject(mHandle, i, bo); + mBufferObjects[i] = bo; + } } } } else { - // add buffer objects for indices and weights + // in advanced skinning mode, we manage the BONE_INDICES and BONE_WEIGHTS arrays ourselves, + // so we have to set the corresponding buffer objects. if (mAdvancedSkinningEnabled) { - for (size_t i = mBufferCount - 2; i < mBufferCount; ++i) { - BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], - backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); - driver.setVertexBufferObject(mHandle, i, bo); - mBufferObjects[i] = bo; + for (auto index : { VertexAttribute::BONE_INDICES, VertexAttribute::BONE_WEIGHTS }) { + size_t const i = mAttributes[index].buffer; + assert_invariant(i != Attribute::BUFFER_UNUSED); + assert_invariant(bufferSizes[i] > 0); + if (!mBufferObjects[i]) { + BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], + backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); + driver.setVertexBufferObject(mHandle, i, bo); + mBufferObjects[i] = bo; + } } } } - } void FVertexBuffer::terminate(FEngine& engine) { @@ -249,6 +319,7 @@ void FVertexBuffer::terminate(FEngine& engine) { } } driver.destroyVertexBuffer(mHandle); + engine.getVertexBufferInfoFactory().destroy(driver, mVertexBufferInfoHandle); } size_t FVertexBuffer::getVertexCount() const noexcept { @@ -284,25 +355,22 @@ void FVertexBuffer::setBufferObjectAt(FEngine& engine, uint8_t bufferIndex, } void FVertexBuffer::updateBoneIndicesAndWeights(FEngine& engine, - std::unique_ptr skinJoints, - std::unique_ptr skinWeights) { - + std::unique_ptr skinJoints, + std::unique_ptr skinWeights) { ASSERT_PRECONDITION(mAdvancedSkinningEnabled, "No advanced skinning enabled"); auto jointsData = skinJoints.release(); - auto bdJoints = BufferDescriptor( - jointsData, mVertexCount * 8, - [](void *buffer, size_t size, void *user) { - delete[] static_cast(buffer); }); - engine.getDriverApi().updateBufferObject(mBufferObjects[mBufferCount - 2], - std::move(bdJoints), 0); + uint8_t const indicesIndex = mAttributes[VertexAttribute::BONE_INDICES].buffer; + engine.getDriverApi().updateBufferObject(mBufferObjects[indicesIndex], + {jointsData, mVertexCount * 8, + [](void* buffer, size_t, void*) { delete[] static_cast(buffer); }}, + 0); auto weightsData = skinWeights.release(); - auto bdWeights = BufferDescriptor( - weightsData, mVertexCount * 16, - [](void *buffer, size_t size, void *user) { - delete[] static_cast(buffer); }); - engine.getDriverApi().updateBufferObject(mBufferObjects[mBufferCount - 1], - std::move(bdWeights), 0); - + uint8_t const weightsIndex = mAttributes[VertexAttribute::BONE_WEIGHTS].buffer; + engine.getDriverApi().updateBufferObject(mBufferObjects[weightsIndex], + {weightsData, mVertexCount * 16, + [](void* buffer, size_t, void*) { delete[] static_cast(buffer); }}, + 0); } + } // namespace filament diff --git a/filament/src/details/VertexBuffer.h b/filament/src/details/VertexBuffer.h index d9e78835186..759c7fe0198 100644 --- a/filament/src/details/VertexBuffer.h +++ b/filament/src/details/VertexBuffer.h @@ -40,6 +40,7 @@ class FEngine; class FVertexBuffer : public VertexBuffer { public: + using VertexBufferInfoHandle = backend::VertexBufferInfoHandle; using VertexBufferHandle = backend::VertexBufferHandle; using BufferObjectHandle = backend::BufferObjectHandle; @@ -51,6 +52,8 @@ class FVertexBuffer : public VertexBuffer { VertexBufferHandle getHwHandle() const noexcept { return mHandle; } + VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; } + size_t getVertexCount() const noexcept; AttributeBitset getDeclaredAttributes() const noexcept { @@ -69,13 +72,9 @@ class FVertexBuffer : public VertexBuffer { private: friend class VertexBuffer; - - struct AttributeData : backend::Attribute { - AttributeData() : backend::Attribute{ .type = backend::ElementType::FLOAT4 } {} - }; - + VertexBufferInfoHandle mVertexBufferInfoHandle; VertexBufferHandle mHandle; - std::array mAttributes; + backend::AttributeArray mAttributes; std::array mBufferObjects; AttributeBitset mDeclaredAttributes; uint32_t mVertexCount = 0; diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index b02d50c5c7d..9ce255332d1 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -20,6 +20,7 @@ #include "Froxelizer.h" #include "RenderPrimitive.h" #include "ResourceAllocator.h" +#include "ShadowMapManager.h" #include "details/Engine.h" #include "details/IndirectLight.h" @@ -43,6 +44,7 @@ #include #include +#include #include using namespace utils; @@ -58,9 +60,9 @@ static constexpr float PID_CONTROLLER_Kd = 0.0f; FView::FView(FEngine& engine) : mFroxelizer(engine), mFogEntity(engine.getEntityManager().create()), - mIsStereoSupported(engine.getDriverApi().isStereoSupported()), - mPerViewUniforms(engine), - mShadowMapManager(engine) { + mIsStereoSupported(engine.getDriverApi().isStereoSupported(engine.getConfig().stereoscopicType)), + mPerViewUniforms(engine) { + DriverApi& driver = engine.getDriverApi(); FDebugRegistry& debugRegistry = engine.getDebugRegistry(); @@ -76,7 +78,11 @@ FView::FView(FEngine& engine) #ifndef NDEBUG debugRegistry.registerDataSource("d.view.frame_info", - mDebugFrameHistory.data(), mDebugFrameHistory.size()); + [this]() -> DebugRegistry::DataSource { + assert_invariant(!mDebugFrameHistory); + mDebugFrameHistory = std::make_unique>(); + return { mDebugFrameHistory->data(), mDebugFrameHistory->size() }; + }); debugRegistry.registerProperty("d.view.pid.kp", &engine.debug.view.pid.kp); debugRegistry.registerProperty("d.view.pid.ki", &engine.debug.view.pid.ki); debugRegistry.registerProperty("d.view.pid.kd", &engine.debug.view.pid.kd); @@ -113,7 +119,8 @@ void FView::terminate(FEngine& engine) { driver.destroyBufferObject(mLightUbh); driver.destroyBufferObject(mRenderableUbh); drainFrameHistory(engine); - mShadowMapManager.terminate(engine); + + ShadowMapManager::terminate(engine, mShadowMapManager); mPerViewUniforms.terminate(driver); mFroxelizer.terminate(driver); @@ -242,21 +249,24 @@ float2 FView::updateScale(FEngine& engine, #ifndef NDEBUG // only for debugging... - using duration_ms = std::chrono::duration; - const float target = (1000.0f * float(frameRateOptions.interval)) / displayInfo.refreshRate; - const float targetWithHeadroom = target * (1.0f - frameRateOptions.headRoomRatio); - std::move(mDebugFrameHistory.begin() + 1, - mDebugFrameHistory.end(), mDebugFrameHistory.begin()); - mDebugFrameHistory.back() = { - .target = target, - .targetWithHeadroom = targetWithHeadroom, - .frameTime = std::chrono::duration_cast(info.frameTime).count(), - .frameTimeDenoised = std::chrono::duration_cast(info.denoisedFrameTime).count(), - .scale = mScale.x * mScale.y, - .pid_e = mPidController.getError(), - .pid_i = mPidController.getIntegral(), - .pid_d = mPidController.getDerivative() - }; + if (mDebugFrameHistory) { + using namespace std::chrono; + using duration_ms = duration; + const float target = (1000.0f * float(frameRateOptions.interval)) / displayInfo.refreshRate; + const float targetWithHeadroom = target * (1.0f - frameRateOptions.headRoomRatio); + std::move(mDebugFrameHistory->begin() + 1, + mDebugFrameHistory->end(), mDebugFrameHistory->begin()); + mDebugFrameHistory->back() = { + .target = target, + .targetWithHeadroom = targetWithHeadroom, + .frameTime = duration_cast(info.frameTime).count(), + .frameTimeDenoised = duration_cast(info.denoisedFrameTime).count(), + .scale = mScale.x * mScale.y, + .pid_e = mPidController.getError(), + .pid_i = mPidController.getIntegral(), + .pid_d = mPidController.getDerivative() + }; + } #endif return mScale; @@ -281,10 +291,10 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD return; } - mShadowMapManager.reset(); - auto& lcm = engine.getLightManager(); + ShadowMapManager::Builder builder; + // dominant directional light is always as index 0 FLightManager::Instance const directionalLight = lightData.elementAt(0); const bool hasDirectionalShadows = directionalLight && lcm.isShadowCaster(directionalLight); @@ -292,7 +302,7 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD const auto& shadowOptions = lcm.getShadowOptions(directionalLight); assert_invariant(shadowOptions.shadowCascades >= 1 && shadowOptions.shadowCascades <= CONFIG_MAX_SHADOW_CASCADES); - mShadowMapManager.setDirectionalShadowMap(0, &shadowOptions); + builder.directionalShadowMap(0, &shadowOptions); } // Find all shadow-casting spotlights. @@ -313,6 +323,10 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD } if (UTILS_LIKELY(!lcm.isShadowCaster(li))) { + // Because we early exit here, we need to make sure we mark the light as non-casting. + // See `ShadowMapManager::updateSpotShadowMaps` for const_cast<> justification. + const_cast( + lightData.elementAt(l)).castsShadows = false; continue; // doesn't cast shadows } @@ -322,7 +336,7 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD if (shadowMapCount + shadowMapCountNeeded <= CONFIG_MAX_SHADOWMAPS) { shadowMapCount += shadowMapCountNeeded; const auto& shadowOptions = lcm.getShadowOptions(li); - mShadowMapManager.addShadowMap(l, spotLight, &shadowOptions); + builder.shadowMap(l, spotLight, &shadowOptions); } if (shadowMapCount >= CONFIG_MAX_SHADOWMAPS) { @@ -330,15 +344,17 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD } } - auto shadowTechnique = mShadowMapManager.update(engine, *this, cameraInfo, - renderableData, lightData); + if (builder.hasShadowMaps()) { + ShadowMapManager::createIfNeeded(engine, mShadowMapManager); + auto shadowTechnique = mShadowMapManager->update(builder, engine, *this, + cameraInfo, renderableData, lightData); - mHasShadowing = any(shadowTechnique); - mNeedsShadowMap = any(shadowTechnique & ShadowMapManager::ShadowTechnique::SHADOW_MAP); + mHasShadowing = any(shadowTechnique); + mNeedsShadowMap = any(shadowTechnique & ShadowMapManager::ShadowTechnique::SHADOW_MAP); + } } -void FView::prepareLighting(FEngine& engine, ArenaScope& arena, - CameraInfo const& cameraInfo) noexcept { +void FView::prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexcept { SYSTRACE_CALL(); SYSTRACE_CONTEXT(); @@ -350,7 +366,7 @@ void FView::prepareLighting(FEngine& engine, ArenaScope& arena, */ if (hasDynamicLighting()) { - scene->prepareDynamicLights(cameraInfo, arena, mLightUbh); + scene->prepareDynamicLights(cameraInfo, mLightUbh); } // here the array of visible lights has been shrunk to CONFIG_MAX_LIGHT_COUNT @@ -423,7 +439,7 @@ CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept { return { *camera, mat4{ rotation } * mat4::translation(translation) }; } -void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena, +void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootArenaScope, filament::Viewport viewport, CameraInfo cameraInfo, float4 const& userTime, bool needsAlphaChannel) noexcept { @@ -461,7 +477,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena, * Gather all information needed to render this scene. Apply the world origin to all * objects in the scene. */ - scene->prepare(js, arena.getAllocator(), + scene->prepare(js, rootArenaScope, cameraInfo.worldTransform, hasVSM()); @@ -471,14 +487,22 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena, JobSystem::Job* froxelizeLightsJob = nullptr; JobSystem::Job* prepareVisibleLightsJob = nullptr; - if (scene->getLightData().size() > FScene::DIRECTIONAL_LIGHTS_COUNT) { + size_t const lightCount = scene->getLightData().size(); + if (lightCount > FScene::DIRECTIONAL_LIGHTS_COUNT) { // create and start the prepareVisibleLights job // note: this job updates LightData (non const) + // allocate a scratch buffer for distances outside the job below, so we don't need + // to use a locked allocator; the downside is that we need to account for the worst case. + size_t const positionalLightCount = lightCount - FScene::DIRECTIONAL_LIGHTS_COUNT; + float* const distances = rootArenaScope.allocate( + (positionalLightCount + 3u) & ~3u, CACHELINE_SIZE); + prepareVisibleLightsJob = js.runAndRetain(js.createJob(nullptr, - [&engine, &arena, &viewMatrix = cameraInfo.view, &cullingFrustum, + [&engine, distances, positionalLightCount, &viewMatrix = cameraInfo.view, &cullingFrustum, &lightData = scene->getLightData()] (JobSystem&, JobSystem::Job*) { - FView::prepareVisibleLights(engine.getLightManager(), arena, + FView::prepareVisibleLights(engine.getLightManager(), + { distances, distances + positionalLightCount }, viewMatrix, cullingFrustum, lightData); })); } @@ -526,7 +550,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena, // As soon as prepareVisibleLight finishes, we can kick-off the froxelization if (hasDynamicLighting()) { auto& froxelizer = mFroxelizer; - if (froxelizer.prepare(driver, arena, viewport, + if (froxelizer.prepare(driver, rootArenaScope, viewport, cameraInfo.projection, cameraInfo.zn, cameraInfo.zf)) { // TODO: might be more consistent to do this in prepareLighting(), but it's not // strictly necessary @@ -602,7 +626,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena, uint32_t(endDirCastersOnly - beginRenderables)}; merged = { 0, uint32_t(endPotentialSpotCastersOnly - beginRenderables) }; - if (!mShadowMapManager.hasSpotShadows()) { + if (!needsShadowMap() || !mShadowMapManager->hasSpotShadows()) { // we know we don't have spot shadows, we can reduce the range to not even include // the potential spot casters merged = { 0, uint32_t(endDirCastersOnly - beginRenderables) }; @@ -641,7 +665,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena, * Relies on FScene::prepare() and prepareVisibleLights() */ - prepareLighting(engine, arena, cameraInfo); + prepareLighting(engine, cameraInfo); /* * Update driver state @@ -662,15 +686,17 @@ void FView::bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noe mPerViewUniforms.bind(driver); if (UTILS_UNLIKELY(driver.getFeatureLevel() == backend::FeatureLevel::FEATURE_LEVEL_0)) { - // FIXME: should be okay to use driver (instead of engine) for FEATURE_LEVEL_0 checks return; } driver.bindUniformBuffer(+UniformBindingPoints::LIGHTS, mLightUbh); - driver.bindUniformBuffer(+UniformBindingPoints::SHADOW, - mShadowMapManager.getShadowUniformsHandle()); + if (needsShadowMap()) { + assert_invariant(mShadowMapManager->getShadowUniformsHandle()); + driver.bindUniformBuffer(+UniformBindingPoints::SHADOW, + mShadowMapManager->getShadowUniformsHandle()); + } driver.bindUniformBuffer(+UniformBindingPoints::FROXEL_RECORDS, mFroxelizer.getRecordBuffer()); @@ -722,11 +748,22 @@ UTILS_NOINLINE }); } -void FView::prepareUpscaler(float2 scale) const noexcept { +void FView::prepareUpscaler(float2 scale, + TemporalAntiAliasingOptions const& taaOptions, + DynamicResolutionOptions const& dsrOptions) const noexcept { SYSTRACE_CALL(); - const float bias = (mDynamicResolution.quality >= QualityLevel::HIGH) ? - std::log2(std::min(scale.x, scale.y)) : 0.0f; - mPerViewUniforms.prepareLodBias(bias); + float bias = 0.0f; + float2 derivativesScale{ 1.0f }; + if (dsrOptions.enabled && dsrOptions.quality >= QualityLevel::HIGH) { + bias = std::log2(std::min(scale.x, scale.y)); + } + if (taaOptions.enabled) { + bias += taaOptions.lodBias; + if (taaOptions.upscaling) { + derivativesScale = 0.5f; + } + } + mPerViewUniforms.prepareLodBias(bias, derivativesScale); } void FView::prepareCamera(FEngine& engine, const CameraInfo& cameraInfo) const noexcept { @@ -747,9 +784,11 @@ void FView::prepareSSAO(Handle ssao) const noexcept { mPerViewUniforms.prepareSSAO(ssao, mAmbientOcclusionOptions); } -void FView::prepareSSR(Handle ssr, float refractionLodOffset, +void FView::prepareSSR(Handle ssr, + bool disableSSR, + float refractionLodOffset, ScreenSpaceReflectionsOptions const& ssrOptions) const noexcept { - mPerViewUniforms.prepareSSR(ssr, refractionLodOffset, ssrOptions); + mPerViewUniforms.prepareSSR(ssr, disableSSR, refractionLodOffset, ssrOptions); } void FView::prepareStructure(Handle structure) const noexcept { @@ -758,7 +797,12 @@ void FView::prepareStructure(Handle structure) const noexcept { } void FView::prepareShadow(Handle texture) const noexcept { - const auto& uniforms = mShadowMapManager.getShadowMappingUniforms(); + // when needsShadowMap() is not set, this method only just sets a dummy texture + // in the needed samplers (in that case `texture` is actually a dummy texture). + ShadowMapManager::ShadowMappingUniforms uniforms; + if (needsShadowMap()) { + uniforms = mShadowMapManager->getShadowMappingUniforms(); + } switch (mShadowType) { case filament::ShadowType::PCF: mPerViewUniforms.prepareShadowPCF(texture, uniforms); @@ -834,7 +878,8 @@ void FView::cullRenderables(JobSystem&, functor(0, renderableData.size()); } -void FView::prepareVisibleLights(FLightManager const& lcm, ArenaScope& rootArena, +void FView::prepareVisibleLights(FLightManager const& lcm, + utils::Slice scratch, mat4f const& viewMatrix, Frustum const& frustum, FScene::LightSoa& lightData) noexcept { SYSTRACE_CALL(); @@ -902,28 +947,25 @@ void FView::prepareVisibleLights(FLightManager const& lcm, ArenaScope& rootArena * - This helps our limited numbers of spot-shadow as well. */ - ArenaScope arena(rootArena.getAllocator()); - size_t const size = visibleLightCount; // number of point/spotlights - size_t const positionalLightCount = size - FScene::DIRECTIONAL_LIGHTS_COUNT; + size_t const positionalLightCount = visibleLightCount - FScene::DIRECTIONAL_LIGHTS_COUNT; if (positionalLightCount) { - // always allocate at least 4 entries, because the vectorized loops below rely on that - float* const UTILS_RESTRICT distances = - arena.allocate((size + 3u) & ~3u, CACHELINE_SIZE); - + assert_invariant(positionalLightCount <= scratch.size()); // pre-compute the lights' distance to the camera, for sorting below // - we don't skip the directional light, because we don't care, it's ignored during sorting + float* const UTILS_RESTRICT distances = scratch.data(); float4 const* const UTILS_RESTRICT spheres = lightData.data(); - computeLightCameraDistances(distances, viewMatrix, spheres, size); + computeLightCameraDistances(distances, viewMatrix, spheres, visibleLightCount); // skip directional light Zip2Iterator b = { lightData.begin(), distances }; - std::sort(b + FScene::DIRECTIONAL_LIGHTS_COUNT, b + size, + std::sort(b + FScene::DIRECTIONAL_LIGHTS_COUNT, b + visibleLightCount, [](auto const& lhs, auto const& rhs) { return lhs.second < rhs.second; }); } // drop excess lights - lightData.resize(std::min(size, CONFIG_MAX_LIGHT_COUNT + FScene::DIRECTIONAL_LIGHTS_COUNT)); + lightData.resize(std::min(visibleLightCount, + CONFIG_MAX_LIGHT_COUNT + FScene::DIRECTIONAL_LIGHTS_COUNT)); } // These methods need to exist so clang honors the __restrict__ keyword, which in turn @@ -956,8 +998,10 @@ void FView::updatePrimitivesLod(FEngine& engine, const CameraInfo&, } FrameGraphId FView::renderShadowMaps(FEngine& engine, FrameGraph& fg, - CameraInfo const& cameraInfo, float4 const& userTime, RenderPass const& pass) noexcept { - return mShadowMapManager.render(engine, fg, pass, *this, cameraInfo, userTime); + CameraInfo const& cameraInfo, float4 const& userTime, + RenderPassBuilder const& passBuilder) noexcept { + assert_invariant(needsShadowMap()); + return mShadowMapManager->render(engine, fg, passBuilder, *this, cameraInfo, userTime); } void FView::commitFrameHistory(FEngine& engine) noexcept { @@ -980,15 +1024,15 @@ void FView::drainFrameHistory(FEngine& engine) noexcept { } void FView::executePickingQueries(backend::DriverApi& driver, - backend::RenderTargetHandle handle, float scale) noexcept { + backend::RenderTargetHandle handle, float2 scale) noexcept { while (mActivePickingQueriesList) { FPickingQuery* const pQuery = mActivePickingQueriesList; mActivePickingQueriesList = pQuery->next; // adjust for dynamic resolution and structure buffer scale - const uint32_t x = uint32_t(float(pQuery->x) * (scale * mScale.x)); - const uint32_t y = uint32_t(float(pQuery->y) * (scale * mScale.y)); + const uint32_t x = uint32_t(float(pQuery->x) * scale.x); + const uint32_t y = uint32_t(float(pQuery->y) * scale.y); if (UTILS_UNLIKELY(driver.getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) { driver.readPixels(handle, x, y, 1, 1, { @@ -1125,9 +1169,7 @@ View::PickingQuery& FView::pick(uint32_t x, uint32_t y, backend::CallbackHandler return *pQuery; } -void FView::setStereoscopicOptions(const StereoscopicOptions& options) { - ASSERT_PRECONDITION(!options.enabled || mIsStereoSupported, - "Stereo rendering is not supported."); +void FView::setStereoscopicOptions(const StereoscopicOptions& options) noexcept { mStereoscopicOptions = options; } diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 50a6c6bb52e..d707001a421 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -53,6 +53,9 @@ #include #include +#include +#include + namespace utils { class JobSystem; } // namespace utils; @@ -88,7 +91,7 @@ class FView : public View { // note: viewport/cameraInfo are passed by value to make it clear that prepare cannot // keep references on them that would outlive the scope of prepare() (e.g. with JobSystem). - void prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& arena, + void prepare(FEngine& engine, backend::DriverApi& driver, RootArenaScope& rootArenaScope, filament::Viewport viewport, CameraInfo cameraInfo, math::float4 const& userTime, bool needsAlphaChannel) noexcept; @@ -133,7 +136,9 @@ class FView : public View { return mName.c_str_safe(); } - void prepareUpscaler(math::float2 scale) const noexcept; + void prepareUpscaler(math::float2 scale, + TemporalAntiAliasingOptions const& taaOptions, + DynamicResolutionOptions const& dsrOptions) const noexcept; void prepareCamera(FEngine& engine, const CameraInfo& cameraInfo) const noexcept; void prepareViewport( @@ -142,10 +147,11 @@ class FView : public View { void prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableData, FScene::LightSoa const& lightData, CameraInfo const& cameraInfo) noexcept; - void prepareLighting(FEngine& engine, ArenaScope& arena, CameraInfo const& cameraInfo) noexcept; + void prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexcept; void prepareSSAO(backend::Handle ssao) const noexcept; - void prepareSSR(backend::Handle ssr, float refractionLodOffset, + void prepareSSR(backend::Handle ssr, bool disableSSR, + float refractionLodOffset, ScreenSpaceReflectionsOptions const& ssrOptions) const noexcept; void prepareStructure(backend::Handle structure) const noexcept; void prepareShadow(backend::Handle structure) const noexcept; @@ -167,11 +173,13 @@ class FView : public View { bool hasDPCF() const noexcept { return mShadowType == ShadowType::DPCF; } bool hasPCSS() const noexcept { return mShadowType == ShadowType::PCSS; } bool hasPicking() const noexcept { return mActivePickingQueriesList != nullptr; } - bool hasInstancedStereo() const noexcept { return mStereoscopicOptions.enabled; } + bool hasStereo() const noexcept { + return mIsStereoSupported && mStereoscopicOptions.enabled; + } FrameGraphId renderShadowMaps(FEngine& engine, FrameGraph& fg, CameraInfo const& cameraInfo, math::float4 const& userTime, - RenderPass const& pass) noexcept; + RenderPassBuilder const& passBuilder) noexcept; void updatePrimitivesLod( FEngine& engine, const CameraInfo& camera, @@ -191,10 +199,11 @@ class FView : public View { bool isStencilBufferEnabled() const noexcept { return mStencilBufferEnabled; } - void setStereoscopicOptions(StereoscopicOptions const& options); + void setStereoscopicOptions(StereoscopicOptions const& options) noexcept; - FCamera const* getDirectionalLightCamera() const noexcept { - return &mShadowMapManager.getShadowMap(0)->getDebugCamera(); + FCamera const* getDirectionalShadowCamera() const noexcept { + if (!mShadowMapManager) return nullptr; + return mShadowMapManager->getDirectionalShadowCamera(); } void setRenderTarget(FRenderTarget* renderTarget) noexcept { @@ -414,7 +423,7 @@ class FView : public View { View::PickingQueryResultCallback callback) noexcept; void executePickingQueries(backend::DriverApi& driver, - backend::RenderTargetHandle handle, float scale) noexcept; + backend::RenderTargetHandle handle, math::float2 scale) noexcept; void setMaterialGlobal(uint32_t index, math::float4 const& value); @@ -455,7 +464,8 @@ class FView : public View { void prepareVisibleRenderables(utils::JobSystem& js, Frustum const& frustum, FScene::RenderableSoa& renderableData) const noexcept; - static void prepareVisibleLights(FLightManager const& lcm, ArenaScope& rootArena, + static void prepareVisibleLights(FLightManager const& lcm, + utils::Slice scratch, math::mat4f const& viewMatrix, Frustum const& frustum, FScene::LightSoa& lightData) noexcept; @@ -485,7 +495,7 @@ class FView : public View { FScene* mScene = nullptr; // The camera set by the user, used for culling and viewing - FCamera* mCullingCamera = nullptr; + FCamera* /* UTILS_NONNULL */ mCullingCamera = nullptr; // FIXME: should alaways be non-null // The optional (debug) camera, used only for viewing FCamera* mViewingCamera = nullptr; @@ -549,7 +559,7 @@ class FView : public View { mutable bool mHasShadowing = false; mutable bool mNeedsShadowMap = false; - ShadowMapManager mShadowMapManager; + std::unique_ptr mShadowMapManager; std::array mMaterialGlobals = {{ { 0, 0, 0, 1 }, @@ -559,7 +569,7 @@ class FView : public View { }}; #ifndef NDEBUG - std::array mDebugFrameHistory; + std::unique_ptr> mDebugFrameHistory; #endif }; diff --git a/filament/src/fg/FrameGraph.cpp b/filament/src/fg/FrameGraph.cpp index ae8a5b8e920..00deb7dc7b8 100644 --- a/filament/src/fg/FrameGraph.cpp +++ b/filament/src/fg/FrameGraph.cpp @@ -16,17 +16,31 @@ #include "fg/FrameGraph.h" #include "fg/details/PassNode.h" +#include "fg/details/Resource.h" #include "fg/details/ResourceNode.h" #include "fg/details/DependencyGraph.h" +#include "FrameGraphId.h" +#include "FrameGraphPass.h" +#include "FrameGraphRenderPass.h" +#include "FrameGraphTexture.h" + #include "details/Engine.h" #include #include +#include +#include +#include #include #include +#include +#include + +#include + namespace filament { inline FrameGraph::Builder::Builder(FrameGraph& fg, PassNode* passNode) noexcept @@ -59,9 +73,10 @@ FrameGraphId FrameGraph::Builder::declareRenderPass( // ------------------------------------------------------------------------------------------------ -FrameGraph::FrameGraph(ResourceAllocatorInterface& resourceAllocator) +FrameGraph::FrameGraph(ResourceAllocatorInterface& resourceAllocator, Mode mode) : mResourceAllocator(resourceAllocator), mArena("FrameGraph Arena", 262144), + mMode(mode), mResourceSlots(mArena), mResources(mArena), mResourceNodes(mArena), @@ -181,6 +196,7 @@ void FrameGraph::execute(backend::DriverApi& driver) noexcept { SYSTRACE_CALL(); + bool const useProtectedMemory = mMode == Mode::PROTECTED; auto const& passNodes = mPassNodes; auto& resourceAllocator = mResourceAllocator; @@ -200,11 +216,11 @@ void FrameGraph::execute(backend::DriverApi& driver) noexcept { // devirtualize resourcesList for (VirtualResource* resource : node->devirtualize) { assert_invariant(resource->first == node); - resource->devirtualize(resourceAllocator); + resource->devirtualize(resourceAllocator, useProtectedMemory); } // call execute - FrameGraphResources resources(*this, *node); + FrameGraphResources const resources(*this, *node); node->execute(resources, driver); // destroy concrete resources diff --git a/filament/src/fg/FrameGraph.h b/filament/src/fg/FrameGraph.h index 4538801bed6..0e01379a014 100644 --- a/filament/src/fg/FrameGraph.h +++ b/filament/src/fg/FrameGraph.h @@ -218,7 +218,13 @@ class FrameGraph { // -------------------------------------------------------------------------------------------- - explicit FrameGraph(ResourceAllocatorInterface& resourceAllocator); + enum class Mode { + UNPROTECTED, + PROTECTED, + }; + + explicit FrameGraph(ResourceAllocatorInterface& resourceAllocator, + Mode mode = Mode::UNPROTECTED); FrameGraph(FrameGraph const&) = delete; FrameGraph& operator=(FrameGraph const&) = delete; ~FrameGraph() noexcept; @@ -517,6 +523,7 @@ class FrameGraph { ResourceAllocatorInterface& mResourceAllocator; LinearAllocatorArena mArena; DependencyGraph mGraph; + const Mode mMode; Vector mResourceSlots; Vector mResources; @@ -527,7 +534,7 @@ class FrameGraph { template FrameGraphPass& FrameGraph::addPass(char const* name, Setup setup, Execute&& execute) { - static_assert(sizeof(Execute) < 1024, "Execute() lambda is capturing too much data."); + static_assert(sizeof(Execute) < 2048, "Execute() lambda is capturing too much data."); // create the FrameGraph pass auto* const pass = mArena.make>(std::forward(execute)); 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/FrameGraphTexture.cpp b/filament/src/fg/FrameGraphTexture.cpp index 2212b78c56e..4785867a43c 100644 --- a/filament/src/fg/FrameGraphTexture.cpp +++ b/filament/src/fg/FrameGraphTexture.cpp @@ -23,7 +23,12 @@ namespace filament { void FrameGraphTexture::create(ResourceAllocatorInterface& resourceAllocator, const char* name, - FrameGraphTexture::Descriptor const& descriptor, FrameGraphTexture::Usage usage) noexcept { + FrameGraphTexture::Descriptor const& descriptor, FrameGraphTexture::Usage usage, + bool useProtectedMemory) noexcept { + if (useProtectedMemory) { + // FIXME: I think we should restrict this to attachments and blit destinations only + usage |= FrameGraphTexture::Usage::PROTECTED; + } std::array swizzle = { descriptor.swizzle.r, descriptor.swizzle.g, diff --git a/filament/src/fg/FrameGraphTexture.h b/filament/src/fg/FrameGraphTexture.h index 99a86d87e38..cb785bb7b6b 100644 --- a/filament/src/fg/FrameGraphTexture.h +++ b/filament/src/fg/FrameGraphTexture.h @@ -34,7 +34,8 @@ namespace filament { * struct SubResourceDescriptor; * a Usage bitmask * And declares and define: - * void create(ResourceAllocatorInterface&, const char* name, Descriptor const&, Usage) noexcept; + * void create(ResourceAllocatorInterface&, const char* name, Descriptor const&, Usage, + * bool useProtectedMemory) noexcept; * void destroy(ResourceAllocatorInterface&) noexcept; */ struct FrameGraphTexture { @@ -78,7 +79,7 @@ struct FrameGraphTexture { * @param descriptor Descriptor to the resource */ void create(ResourceAllocatorInterface& resourceAllocator, const char* name, - Descriptor const& descriptor, Usage usage) noexcept; + Descriptor const& descriptor, Usage usage, bool useProtectedMemory) noexcept; /** * Destroy the concrete resource diff --git a/filament/src/fg/PassNode.cpp b/filament/src/fg/PassNode.cpp index 071e3616fc6..cf76ea35b76 100644 --- a/filament/src/fg/PassNode.cpp +++ b/filament/src/fg/PassNode.cpp @@ -82,22 +82,22 @@ uint32_t RenderPassNode::declareRenderTarget(FrameGraph& fg, FrameGraph::Builder RenderPassData data; data.name = name; data.descriptor = descriptor; - FrameGraphRenderPass::Attachments const& attachments = data.descriptor.attachments; // retrieve the ResourceNode of the attachments coming to us -- this will be used later // to compute the discard flags. DependencyGraph const& dependencyGraph = fg.getGraph(); auto incomingEdges = dependencyGraph.getIncomingEdges(this); - auto outgoingEdges = dependencyGraph.getOutgoingEdges(this); for (size_t i = 0; i < RenderPassData::ATTACHMENT_COUNT; i++) { - if (descriptor.attachments.array[i]) { - data.attachmentInfo[i] = attachments.array[i]; + FrameGraphId const& handle = + data.descriptor.attachments.array[i]; + if (handle) { + data.attachmentInfo[i] = handle; // TODO: this is not very efficient auto incomingPos = std::find_if(incomingEdges.begin(), incomingEdges.end(), - [&dependencyGraph, handle = descriptor.attachments.array[i]] + [&dependencyGraph, handle] (DependencyGraph::Edge const* edge) { ResourceNode const* node = static_cast( dependencyGraph.getNode(edge->from)); @@ -111,7 +111,7 @@ uint32_t RenderPassNode::declareRenderTarget(FrameGraph& fg, FrameGraph::Builder } // this could be either outgoing or incoming (if there are no outgoing) - data.outgoing[i] = fg.getActiveResourceNode(descriptor.attachments.array[i]); + data.outgoing[i] = fg.getActiveResourceNode(handle); if (data.outgoing[i] == data.incoming[i]) { data.outgoing[i] = nullptr; } @@ -229,6 +229,11 @@ void RenderPassNode::resolve() noexcept { rt.descriptor.samples = pImportedRenderTarget->importedDesc.samples; rt.backend.target = pImportedRenderTarget->target; + // We could end-up here more than once, for instance if the rendertarget is used + // by multiple passes (this would imply a read-back, btw). In this case, we don't want + // to clear it the 2nd time, so we clear the imported pass's clear flags. + pImportedRenderTarget->importedDesc.clearFlags = TargetBufferFlags::NONE; + // but don't discard attachments the imported target tells us to keep rt.backend.params.flags.discardStart &= ~pImportedRenderTarget->importedDesc.keepOverrideStart; rt.backend.params.flags.discardEnd &= ~pImportedRenderTarget->importedDesc.keepOverrideEnd; @@ -271,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/src/fg/details/Resource.h b/filament/src/fg/details/Resource.h index 966b4bc3388..0b9a89954de 100644 --- a/filament/src/fg/details/Resource.h +++ b/filament/src/fg/details/Resource.h @@ -84,7 +84,8 @@ class VirtualResource { ResourceEdgeBase const* writer) noexcept = 0; /* Instantiate the concrete resource */ - virtual void devirtualize(ResourceAllocatorInterface& resourceAllocator) noexcept = 0; + virtual void devirtualize(ResourceAllocatorInterface& resourceAllocator, + bool useProtectedMemory) noexcept = 0; /* Destroy the concrete resource */ virtual void destroy(ResourceAllocatorInterface& resourceAllocator) noexcept = 0; @@ -132,7 +133,7 @@ class Resource : public VirtualResource { Descriptor descriptor; SubResourceDescriptor subResourceDescriptor; - // weather the resource was detached + // whether the resource was detached bool detached = false; // An Edge with added data from this resource @@ -229,9 +230,10 @@ class Resource : public VirtualResource { delete static_cast(edge); } - void devirtualize(ResourceAllocatorInterface& resourceAllocator) noexcept override { + void devirtualize(ResourceAllocatorInterface& resourceAllocator, + bool useProtectedMemory) noexcept override { if (!isSubResource()) { - resource.create(resourceAllocator, name, descriptor, usage); + resource.create(resourceAllocator, name, descriptor, usage, useProtectedMemory); } else { // resource is guaranteed to be initialized before we are by construction resource = static_cast(parent)->resource; @@ -268,7 +270,7 @@ class ImportedResource : public Resource { } protected: - void devirtualize(ResourceAllocatorInterface&) noexcept override { + void devirtualize(ResourceAllocatorInterface&, bool) noexcept override { // imported resources don't need to devirtualize } void destroy(ResourceAllocatorInterface&) noexcept override { diff --git a/filament/src/materials/antiAliasing/taa.mat b/filament/src/materials/antiAliasing/taa.mat index 22a2d0a506c..b5430378c70 100644 --- a/filament/src/materials/antiAliasing/taa.mat +++ b/filament/src/materials/antiAliasing/taa.mat @@ -24,10 +24,62 @@ material { precision: high }, { - type : float[9], + type : float2, + name : jitter, + precision: high + }, + { + type : float4[9], name : filterWeights } ], + constants : [ + { + type : bool, + name : upscaling, + default : false + }, + { + type : bool, + name : historyReprojection, + default : true + }, + { + type : bool, + name : filterHistory, + default : true + }, + { + type : bool, + name : filterInput, + default : true + }, + { + type : int, + name : boxClipping, + default : 0 + }, + { + type : int, + name : boxType, + default : 1 + }, + { + type : bool, + name : useYCoCg, + default : false + }, + { + type : bool, + name : preventFlickering, + default : false + }, + { + type : float, + name : varianceGamma, + default : 1.0 + } + ], variables : [ vertex ], @@ -44,14 +96,6 @@ vertex { fragment { -#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) -#define TEXTURE_SPACE_UP -1 -#define TEXTURE_SPACE_DN 1 -#else -#define TEXTURE_SPACE_UP 1 -#define TEXTURE_SPACE_DN -1 -#endif - /* Clipping box type */ // min/max neighborhood @@ -61,9 +105,6 @@ fragment { // uses both min/max and variance #define BOX_TYPE_AABB_VARIANCE 2 -// High VARIANCE_GAMMA [0.75, 1.25] increases ghosting artefact, lower values increases jittering -#define VARIANCE_GAMMA 1.0 - /* Clipping algorithm */ // accurate box clipping @@ -73,34 +114,41 @@ fragment { // no clipping (for debugging only) #define BOX_CLIPPING_NONE 2 -/* Configuration mobile/desktop */ - -#if FILAMENT_QUALITY < FILAMENT_QUALITY_HIGH -#define BOX_CLIPPING BOX_CLIPPING_ACCURATE -#define BOX_TYPE BOX_TYPE_VARIANCE -#define USE_YCoCg 0 -#define FILTER_INPUT 1 -#define FILTER_HISTORY 0 -#else -#define BOX_CLIPPING BOX_CLIPPING_ACCURATE -#define BOX_TYPE BOX_TYPE_AABB_VARIANCE -#define USE_YCoCg 1 -#define FILTER_INPUT 1 -#define FILTER_HISTORY 1 -#endif +float rcp(float x) { + return 1.0 / x; +} -/* debugging helper */ +float lumaRGB(const vec3 c) { + return luminance(c); +} -#define HISTORY_REPROJECTION 1 -#define PREVENT_FLICKERING 0 // FIXME: thin lines disapear +float lumaYCoCg(const vec3 c) { + return c.x; +} +float luma(const vec3 c) { + return materialConstants_useYCoCg ? lumaYCoCg(c) : lumaRGB(c); +} -float luma(const vec3 color) { -#if USE_YCoCg - return color.x; -#else - return luminance(color); -#endif +vec3 tonemap(const vec3 c) { + return c * rcp(1.0 + max3(c)); +} + +vec4 tonemap(const vec4 c) { + return vec4(c.rgb * rcp(1.0 + max3(c.rgb)), c.a); +} + +vec3 tonemap(const float w, const vec3 c) { + return c * (w * rcp(1.0 + max3(c))); +} + +vec4 tonemap(const float w, const vec4 c) { + return vec4(c.rgb * (w * rcp(1.0 + max3(c.rgb))), c.a); +} + +vec3 untonemap(const vec3 c) { + const float epsilon = 1.0 / 65504.0; + return c * rcp(max(epsilon, 1.0 - max3(c))); } vec3 RGB_YCoCg(const vec3 c) { @@ -145,15 +193,16 @@ vec4 clipToBox(const int quality, // Optimized to 5 taps by removing the corner samples // And modified for mediump support vec4 sampleTextureCatmullRom(const sampler2D tex, const highp vec2 uv, const highp vec2 texSize) { - // We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding - // down the sample location to get the exact center of our "starting" texel. The starting texel will be at - // location [1, 1] in the grid, where [0, 0] is the top left corner. + // We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. + // We'll do this by rounding down the sample location to get the exact center of our "starting" + // texel. The starting texel will be at location [1, 1] in the grid, where [0, 0] is the + // top left corner. highp vec2 samplePos = uv * texSize; highp vec2 texPos1 = floor(samplePos - 0.5) + 0.5; - // Compute the fractional offset from our starting texel to our original sample location, which we'll - // feed into the Catmull-Rom spline function to get our filter weights. + // Compute the fractional offset from our starting texel to our original sample location, + // which we'll feed into the Catmull-Rom spline function to get our filter weights. highp vec2 f = samplePos - texPos1; highp vec2 f2 = f * f; highp vec2 f3 = f2 * f; @@ -186,13 +235,23 @@ vec4 sampleTextureCatmullRom(const sampler2D tex, const highp vec2 uv, const hig float k3 = w3.x * w12.y; float k4 = w12.x * w3.y; - vec4 result = textureLod(tex, vec2(texPos12.x, texPos0.y), 0.0) * k0 - + textureLod(tex, vec2(texPos0.x, texPos12.y), 0.0) * k1 - + textureLod(tex, vec2(texPos12.x, texPos12.y), 0.0) * k2 - + textureLod(tex, vec2(texPos3.x, texPos12.y), 0.0) * k3 - + textureLod(tex, vec2(texPos12.x, texPos3.y), 0.0) * k4; + vec4 s[5]; + s[0] = textureLod(tex, vec2(texPos12.x, texPos0.y), 0.0); + s[1] = textureLod(tex, vec2(texPos0.x, texPos12.y), 0.0); + s[2] = textureLod(tex, vec2(texPos12.x, texPos12.y), 0.0); + s[3] = textureLod(tex, vec2(texPos3.x, texPos12.y), 0.0); + s[4] = textureLod(tex, vec2(texPos12.x, texPos3.y), 0.0); + + vec4 result = k0 * s[0] + + k1 * s[1] + + k2 * s[2] + + k3 * s[3] + + k4 * s[4]; + + result *= rcp(k0 + k1 + k2 + k3 + k4); - result *= 1.0 / (k0 + k1 + k2 + k3 + k4); + // we could end-up with negative values + result = max(vec4(0), result); return result; } @@ -200,127 +259,156 @@ vec4 sampleTextureCatmullRom(const sampler2D tex, const highp vec2 uv, const hig void postProcess(inout PostProcessInputs postProcess) { highp vec4 uv = variable_vertex.xyxy; // interpolated to pixel center - // read the depth buffer center sample for reprojection - float depth = textureLod(materialParams_depth, uv.xy, 0.0).r; - -#if HISTORY_REPROJECTION - uv.zw = uvToRenderTargetUV(uv.zw); - // reproject history to current frame - highp vec4 q = materialParams.reprojection * vec4(uv.zw, depth, 1.0); - uv.zw = (q.xy * (1.0 / q.w)) * 0.5 + 0.5; - uv.zw = uvToRenderTargetUV(uv.zw); -#endif + if (materialConstants_historyReprojection) { + // read the depth buffer center sample for reprojection + highp float depth = textureLod(materialParams_depth, uv.xy, 0.0).r; + // reproject history to current frame + uv.zw = uvToRenderTargetUV(uv.zw); + highp vec4 q = materialParams.reprojection * vec4(uv.zw, depth, 1.0); + uv.zw = (q.xy * (1.0 / q.w)) * 0.5 + 0.5; + uv.zw = uvToRenderTargetUV(uv.zw); + } // read center color and history samples - vec4 color = textureLod(materialParams_color, uv.xy, 0.0); -#if FILTER_HISTORY - vec4 history = sampleTextureCatmullRom(materialParams_history, uv.zw, - vec2(textureSize(materialParams_history, 0))); -#else - vec4 history = textureLod(materialParams_history, uv.zw, 0.0); -#endif + vec4 history; + if (materialConstants_filterHistory) { + history = sampleTextureCatmullRom(materialParams_history, uv.zw, + vec2(textureSize(materialParams_history, 0))); + } else { + history = textureLod(materialParams_history, uv.zw, 0.0); + } -#if USE_YCoCg - history.rgb = RGB_YCoCg(history.rgb); -#endif + if (materialConstants_useYCoCg) { + history.rgb = RGB_YCoCg(history.rgb); + } + + highp vec2 size = vec2(textureSize(materialParams_color, 0)); + highp vec2 p = (floor(uv.xy * size) + 0.5) / size; + vec4 filtered = textureLod(materialParams_color, p, 0.0); - // build the history clamping box vec3 s[9]; - s[0] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2(-1, TEXTURE_SPACE_DN)).rgb; - s[1] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 0, TEXTURE_SPACE_DN)).rgb; - s[2] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 1, TEXTURE_SPACE_DN)).rgb; - s[3] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2(-1, 0)).rgb; - s[4] = color.rgb; - s[5] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 1, 0)).rgb; - s[6] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2(-1, TEXTURE_SPACE_UP)).rgb; - s[7] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 0, TEXTURE_SPACE_UP)).rgb; - s[8] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 1, TEXTURE_SPACE_UP)).rgb; - -#if USE_YCoCg - for (int i = 0 ; i < 9 ; i++) { - s[i] = RGB_YCoCg(s[i]); + if (materialConstants_filterInput || materialConstants_boxClipping != BOX_CLIPPING_NONE) { + s[0] = textureLodOffset(materialParams_color, p, 0.0, ivec2(-1, -1)).rgb; + s[1] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 0, -1)).rgb; + s[2] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 1, -1)).rgb; + s[3] = textureLodOffset(materialParams_color, p, 0.0, ivec2(-1, 0)).rgb; + s[4] = filtered.rgb; + s[5] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 1, 0)).rgb; + s[6] = textureLodOffset(materialParams_color, p, 0.0, ivec2(-1, 1)).rgb; + s[7] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 0, 1)).rgb; + s[8] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 1, 1)).rgb; + if (materialConstants_useYCoCg) { + for (int i = 0; i < 9; i++) { + s[i] = RGB_YCoCg(s[i]); + } + } } - color.rgb = s[4].rgb; -#endif -#if FILTER_INPUT - // unjitter/filter input - vec4 filtered = vec4(0, 0, 0, color.a); - for (int i = 0 ; i < 9 ; i++) { - filtered.rgb += s[i] * materialParams.filterWeights[i]; + vec2 subPixelOffset = p - uv.xy; // +/- [0.25, 0.25] + float confidence = materialConstants_upscaling ? 0.0 : 1.0; + + if (materialConstants_filterInput) { + // unjitter/filter input + // figure out which set of coeficients to use + filtered = vec4(0, 0, 0, filtered.a); + if (materialConstants_upscaling) { + int jxp = subPixelOffset.y > 0.0 ? 3 : 0; + int jxn = subPixelOffset.y > 0.0 ? 2 : 1; + int j = subPixelOffset.x > 0.0 ? jxp : jxn; + for (int i = 0; i < 9; i++) { + float w = materialParams.filterWeights[i][j]; + filtered.rgb += s[i] * w; + confidence = max(confidence, w); + } + } else { + for (int i = 0; i < 9; i++) { + float w = materialParams.filterWeights[i][0]; + filtered.rgb += s[i] * w; + } + } + } else { + if (materialConstants_useYCoCg) { + filtered.rgb = RGB_YCoCg(filtered.rgb); + } + if (materialConstants_upscaling) { + confidence = float(materialParams.jitter.x * subPixelOffset.x > 0.0 && + materialParams.jitter.y * subPixelOffset.y > 0.0); + } } -#else - vec4 filtered = color; -#endif -#if BOX_TYPE == BOX_TYPE_AABB || BOX_TYPE == BOX_TYPE_AABB_VARIANCE - vec3 boxmin = min(s[4], min(min(s[1], s[3]), min(s[5], s[7]))); - vec3 boxmax = max(s[4], max(max(s[1], s[3]), max(s[5], s[7]))); - vec3 box9min = min(boxmin, min(min(s[0], s[2]), min(s[6], s[8]))); - vec3 box9max = max(boxmax, max(max(s[0], s[2]), max(s[6], s[8]))); - // round the corners of the 3x3 box - boxmin = (boxmin + box9min) * 0.5; - boxmax = (boxmax + box9max) * 0.5; -#endif + // build the history clamping box + if (materialConstants_boxClipping != BOX_CLIPPING_NONE) { + vec3 boxmin; + vec3 boxmax; + if (materialConstants_boxType == BOX_TYPE_AABB || + materialConstants_boxType == BOX_TYPE_AABB_VARIANCE) { + boxmin = min(s[4], min(min(s[1], s[3]), min(s[5], s[7]))); + boxmax = max(s[4], max(max(s[1], s[3]), max(s[5], s[7]))); + vec3 box9min = min(boxmin, min(min(s[0], s[2]), min(s[6], s[8]))); + vec3 box9max = max(boxmax, max(max(s[0], s[2]), max(s[6], s[8]))); + // round the corners of the 3x3 box + boxmin = (boxmin + box9min) * 0.5; + boxmax = (boxmax + box9max) * 0.5; + } -#if BOX_TYPE == BOX_TYPE_VARIANCE || BOX_TYPE == BOX_TYPE_AABB_VARIANCE - // "An Excursion in Temporal Supersampling" by Marco Salvi - vec3 m0 = s[4]; - vec3 m1 = s[4] * s[4]; - // we use only 5 samples instead of all 9 - for (int i = 1 ; i < 9 ; i+=2) { - m0 += s[i]; - m1 += s[i] * s[i]; + if (materialConstants_boxType == BOX_TYPE_VARIANCE || + materialConstants_boxType == BOX_TYPE_AABB_VARIANCE) { + // "An Excursion in Temporal Supersampling" by Marco Salvi + highp vec3 m0 = s[4];// conversion to highp + highp vec3 m1 = m0 * m0; + // we use only 5 samples instead of all 9 + for (int i = 1; i < 9; i+=2) { + highp vec3 c = s[i];// conversion to highp + m0 += c; + m1 += c * c; + } + highp vec3 a0 = m0 * (1.0 / 5.0); + highp vec3 a1 = m1 * (1.0 / 5.0); + highp vec3 sigma = sqrt(a1 - a0 * a0); + if (materialConstants_boxType == BOX_TYPE_VARIANCE) { + boxmin = a0 - materialConstants_varianceGamma * sigma; + boxmax = a0 + materialConstants_varianceGamma * sigma; + } else { + // intersect both bounding boxes + boxmin = max(boxmin, a0 - materialConstants_varianceGamma * sigma); + boxmax = min(boxmax, a0 + materialConstants_varianceGamma * sigma); + } + } + // history clamping + history = clipToBox(materialConstants_boxClipping, boxmin, boxmax, filtered, history); } - vec3 a0 = m0 * (1.0 / 5.0); - vec3 a1 = m1 * (1.0 / 5.0); - vec3 sigma = sqrt(a1 - a0 * a0); -#if BOX_TYPE == BOX_TYPE_VARIANCE - vec3 boxmin = a0 - VARIANCE_GAMMA * sigma; - vec3 boxmax = a0 + VARIANCE_GAMMA * sigma; -#else - boxmin = min(boxmin, a0 - VARIANCE_GAMMA * sigma); - boxmax = max(boxmax, a0 + VARIANCE_GAMMA * sigma); -#endif -#endif - - // history clamping - history = clipToBox(BOX_CLIPPING, boxmin, boxmax, filtered, history); - float lumaColor = luma(filtered.rgb); - float lumaHistory = luma(history.rgb); + float alpha = materialParams.alpha * confidence; + if (materialConstants_preventFlickering) { + // [Lottes] prevents flickering by modulating the blend weight by the difference in luma + float lumaColor = luma(filtered.rgb); + float lumaHistory = luma(history.rgb); + float diff = 1.0 - abs(lumaColor - lumaHistory) / (0.001 + max(lumaColor, lumaHistory)); + alpha *= diff * diff; + } - float alpha = materialParams.alpha; -#if PREVENT_FLICKERING - // [Lottes] prevents flickering by modulating the blend weight by the difference in luma - float diff = 1.0 - abs(lumaColor - lumaHistory) / (0.001 + max(lumaColor, lumaHistory)); - alpha *= diff * diff; -#endif + // go back to RGB space before tonemapping + if (materialConstants_useYCoCg) { + filtered.rgb = YCoCg_RGB(filtered.rgb); + history.rgb = YCoCg_RGB(history.rgb); + } - // tonemapping for handling HDR - filtered.rgb *= 1.0 / (1.0 + lumaColor); - history.rgb *= 1.0 / (1.0 + lumaHistory); + // tonemap before mixing + filtered.rgb = tonemap(filtered.rgb); + history.rgb = tonemap(history.rgb); // combine history and current frame vec4 result = mix(history, filtered, alpha); // untonemap result - result.rgb *= 1.0 / (1.0 - luma(result.rgb)); - -#if USE_YCoCg - result.rgb = YCoCg_RGB(result.rgb); -#endif - - // store result (which will becomes new history) - // we could end-up with negative values due to the bicubic filter, which never recover on - // their own. - result = max(vec4(0), result); + result.rgb = untonemap(result.rgb); #if POST_PROCESS_OPAQUE // kill the work performed above result.a = 1.0; #endif + // store result (which will becomes new history) postProcess.color = result; } diff --git a/filament/src/materials/blitArray.mat b/filament/src/materials/blitArray.mat new file mode 100644 index 00000000000..2d7cfc4da3b --- /dev/null +++ b/filament/src/materials/blitArray.mat @@ -0,0 +1,38 @@ +material { + name : blitArray, + parameters : [ + { + type : sampler2dArray, + name : color, + precision: medium + }, + { + type : int, + name : layerIndex + }, + { + type : float4, + name : viewport, + precision: high + } + ], + variables : [ + vertex + ], + depthWrite : false, + depthCulling : false, + domain: postprocess +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw; + postProcess.vertex.xy = uvToRenderTargetUV(postProcess.vertex.xy); + } +} + +fragment { + void postProcess(inout PostProcessInputs postProcess) { + postProcess.color = textureLod(materialParams_color, vec3(variable_vertex.xy, materialParams.layerIndex), 0.0); + } +} diff --git a/filament/src/materials/blitLow.mat b/filament/src/materials/blitLow.mat index 1154305a64b..09e6c79920c 100644 --- a/filament/src/materials/blitLow.mat +++ b/filament/src/materials/blitLow.mat @@ -22,7 +22,8 @@ material { ], depthWrite : false, depthCulling : false, - domain: postprocess + domain: postprocess, + featureLevel : 0 } vertex { @@ -34,7 +35,10 @@ vertex { fragment { void postProcess(inout PostProcessInputs postProcess) { +#if FILAMENT_EFFECTIVE_VERSION == 100 + postProcess.color = texture2D(materialParams_color, variable_vertex.xy); +#else postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0); +#endif } } - diff --git a/filament/src/materials/defaultMaterial0.mat b/filament/src/materials/defaultMaterial0.mat deleted file mode 100644 index 914d29c3d1d..00000000000 --- a/filament/src/materials/defaultMaterial0.mat +++ /dev/null @@ -1,12 +0,0 @@ -material { - name : "Filament Default Material", - shadingModel : unlit, - featureLevel: 0 -} - -fragment { - void material(inout MaterialInputs material) { - prepareMaterial(material); - material.baseColor.rgb = vec3(0.8); - } -} diff --git a/filament/src/materials/fsr/fsr_rcas.mat b/filament/src/materials/fsr/fsr_rcas.mat index 185e62d288c..cfdf0372042 100644 --- a/filament/src/materials/fsr/fsr_rcas.mat +++ b/filament/src/materials/fsr/fsr_rcas.mat @@ -43,6 +43,8 @@ fragment { #define FSR_RCAS_F 1 #if !POST_PROCESS_OPAQUE # define FSR_RCAS_PASSTHROUGH_ALPHA + // todo: make this a spec constant + # define FSR_RCAS_DENOISE #endif #include "ffx_fsr1.h" @@ -61,6 +63,9 @@ fragment { #endif p, materialParams.RcasCon); + // todo: make this an option + postProcess.color.rgb = max(vec3(0), postProcess.color.rgb); + #if POST_PROCESS_OPAQUE postProcess.color.a = 1.0; #endif diff --git a/filament/src/materials/resolveDepth.mat b/filament/src/materials/resolveDepth.mat new file mode 100644 index 00000000000..cdf0787a8d5 --- /dev/null +++ b/filament/src/materials/resolveDepth.mat @@ -0,0 +1,33 @@ +material { + name : resolveDepth, + parameters : [ + { + type : sampler2d, + name : depth, + precision: high, + multisample : true + } + ], + outputs : [ + { + name : depth, + target : depth, + type : float + } + ], + domain : postprocess, + depthWrite : true, + depthCulling : false, + culling: none +} + +fragment { + // resolve depth by keeping the first sample. This is similar to what glBlitFramebuffer() does + // this material is currently only used on Metal and Vulkan. + // Technically it should be feature-level 2 material, but that would restrict when we can use + // it because Vulkan can return feature-level 1 currently (we should fix that). + void postProcess(inout PostProcessInputs postProcess) { + highp ivec2 icoord = ivec2(gl_FragCoord.xy); + postProcess.depth = texelFetch(materialParams_depth, icoord, 0).r; + } +} diff --git a/filament/src/materials/shadowmap.mat b/filament/src/materials/shadowmap.mat new file mode 100644 index 00000000000..edca93b59db --- /dev/null +++ b/filament/src/materials/shadowmap.mat @@ -0,0 +1,59 @@ +material { + name : shadowmap, + parameters : [ + { + type : sampler2dArray, + name : shadowmap, + precision : high + }, + { + type : float, + name : power, + precision : high + }, + { + type : float2, + name : scale, + }, + { + type : int, + name : layer + }, + { + type : int, + name : level + }, + { + type : int, + name : channel + } + ], + variables : [ + vertex + ], + vertexDomain : device, + depthWrite : false, + depthCulling : false, + domain: postprocess +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = postProcess.normalizedUV; + postProcess.position.xy = 1.0 + (postProcess.position.xy - 1.0) * materialParams.scale; + } +} + +fragment { + void dummy(){} + + void postProcess(inout PostProcessInputs postProcess) { + highp vec2 uv = uvToRenderTargetUV(variable_vertex.xy); + highp vec3 p = vec3(uv, float(materialParams.layer)); + float m = float(materialParams.level); + highp float c = textureLod(materialParams_shadowmap, p, m)[materialParams.channel]; + c = pow(abs(c), materialParams.power); + postProcess.color.rgb = vec3(c); + postProcess.color.a = 1.0; + } +} diff --git a/filament/src/materials/skybox.mat b/filament/src/materials/skybox.mat index 1d36108a275..c0a1181dbfa 100644 --- a/filament/src/materials/skybox.mat +++ b/filament/src/materials/skybox.mat @@ -35,10 +35,14 @@ fragment { if (materialParams.constantColor != 0) { sky = materialParams.color; } else { +#if MATERIAL_FEATURE_LEVEL == 0 + sky = vec4(textureCube(materialParams_skybox, variable_eyeDirection.xyz).rgb, 1.0); +#else sky = vec4(textureLod(materialParams_skybox, variable_eyeDirection.xyz, 0.0).rgb, 1.0); +#endif sky.rgb *= frameUniforms.iblLuminance; } - if (materialParams.showSun != 0 && frameUniforms.sun.w >= 0.0f) { + if (materialParams.showSun != 0 && frameUniforms.sun.w >= 0.0) { vec3 direction = normalize(variable_eyeDirection.xyz); // Assume the sun is a sphere vec3 sun = frameUniforms.lightColorIntensity.rgb * @@ -54,6 +58,15 @@ fragment { vertex { void materialVertex(inout MaterialVertexInputs material) { - material.eyeDirection.xyz = material.worldPosition.xyz; + // This code is taken from computeWorldPosition and assumes the vertex domain is 'device'. + vec4 p = getPosition(); + // GL convention to inverted DX convention + p.z = p.z * -0.5 + 0.5; + vec4 worldPosition = getWorldFromClipMatrix() * p; + // Getting the true world position would require dividing by w, but since this is a skybox + // at inifinity, this results in very large numbers for material.eyeDirection. + // Since the eyeDirection is only used as a direction vector in the fragment shader, we can + // skip that step to improve precision. + material.eyeDirection.xyz = worldPosition.xyz; } } diff --git a/filament/src/materials/skybox0.mat b/filament/src/materials/skybox0.mat deleted file mode 100644 index 2891e32f4b3..00000000000 --- a/filament/src/materials/skybox0.mat +++ /dev/null @@ -1,60 +0,0 @@ -material { - name : Skybox, - parameters : [ - { - type : int, - name : showSun - }, - { - type : int, - name : constantColor - }, - { - type : samplerCubemap, - name : skybox - }, - { - type : float4, - name : color - } - ], - variables : [ - eyeDirection - ], - vertexDomain : device, - depthWrite : false, - shadingModel : unlit, - variantFilter : [ skinning, shadowReceiver, vsm ], - culling: none, - featureLevel: 0 -} - -fragment { - void material(inout MaterialInputs material) { - prepareMaterial(material); - vec4 sky; - if (materialParams.constantColor != 0) { - sky = materialParams.color; - } else { - sky = vec4(textureCube(materialParams_skybox, variable_eyeDirection.xyz).rgb, 1.0); - sky.rgb *= frameUniforms.iblLuminance; - } - if (materialParams.showSun != 0 && frameUniforms.sun.w >= 0.0) { - vec3 direction = normalize(variable_eyeDirection.xyz); - // Assume the sun is a sphere - vec3 sun = frameUniforms.lightColorIntensity.rgb * - (frameUniforms.lightColorIntensity.a * (4.0 * PI)); - float cosAngle = dot(direction, frameUniforms.lightDirection); - float x = (cosAngle - frameUniforms.sun.x) * frameUniforms.sun.z; - float gradient = pow(1.0 - saturate(x), frameUniforms.sun.w); - sky.rgb = sky.rgb + gradient * sun; - } - material.baseColor = sky; - } -} - -vertex { - void materialVertex(inout MaterialVertexInputs material) { - material.eyeDirection.xyz = material.worldPosition.xyz; - } -} 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/ide/emacs/c-filament-style.el b/ide/emacs/c-filament-style.el new file mode 100644 index 00000000000..6b25e52e1b7 --- /dev/null +++ b/ide/emacs/c-filament-style.el @@ -0,0 +1,79 @@ +;;; c-filament-style.el --- Filament C++ style -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Google LLC + +;; Author: Eliza Velasquez +;; Version: 0.1.0 +;; Created: 2023-10-31 +;; Package-Requires: ((emacs "24.1")) +;; Keywords: c +;; URL: https://github.com/google/filament +;; SPDX-License-Identifier: Apache-2 + +;;; Commentary: + +;; Defines a basic Filament style for C++ code in Emacs. + +;;; Code: + +(require 'cc-mode) +(defvar c-syntactic-context) + +(defun c-filament-style-lineup-brace-list-intro (langelem) + "Indent first line of braced lists in the Filament style. + +This properly indents doubled-up arglists + lists, e.g. ({. It +also properly indents enums at 1x and other lists at 2x. + +LANGELEM is the cons of the syntactic symbol and the anchor +position (or nil if there is none)." + (save-excursion + (let (case-fold-search) + (goto-char (c-langelem-pos langelem)) + (if (looking-at "enum\\b") + c-basic-offset + (if (assq 'arglist-cont-nonempty c-syntactic-context) + (- c-basic-offset) + (* 2 c-basic-offset)))))) + +(defun c-filament-style-lineup-brace-list-entry (_langelem) + "Indent following lines in braced lists in the Filament style. + +This properly indents doubled-up arglists + lists, e.g. ({." + (if (assq 'arglist-cont-nonempty c-syntactic-context) + (- (* c-basic-offset 2)) + 0)) + +(defun c-filament-style-lineup-arglist (langelem) + "Indent following lines in braced lists in the Filament style. + +This properly indents arglists nested in if statements. LANGELEM +is the cons of the syntactic symbol and the anchor position (or +nil if there is none)." + (save-excursion + (let (case-fold-search) + (goto-char (c-langelem-pos langelem)) + (if (and (cdr c-syntactic-context) + (looking-at "if\\b")) + c-basic-offset + (* 2 c-basic-offset))))) + +(c-add-style "filament" + '((c-basic-offset . 4) + (c-offsets-alist + (innamespace . 0) + (inextern-lang . 0) + (arglist-intro . c-filament-style-lineup-arglist) + (arglist-cont . 0) + (arglist-cont-nonempty . c-filament-style-lineup-arglist) + (arglist-close . c-filament-style-lineup-arglist) + (statement-cont . ++) + (case-label . +) + (brace-list-intro . c-filament-style-lineup-brace-list-intro) + (brace-list-entry . c-filament-style-lineup-brace-list-entry) + (brace-list-close . c-filament-style-lineup-brace-list-entry) + (label . [0])))) + +(provide 'c-filament-style) + +;;; c-filament-style.el ends here diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index b83e7f0e029..8543885f0a3 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.44.0" + spec.version = "1.51.3" 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.44.0/filament-v1.44.0-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.3/filament-v1.51.3-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/ios/samples/gltf-viewer/gltf-viewer/FILModelView.h b/ios/samples/gltf-viewer/gltf-viewer/FILModelView.h index 43d0a36edf4..0bc5d7bc700 100644 --- a/ios/samples/gltf-viewer/gltf-viewer/FILModelView.h +++ b/ios/samples/gltf-viewer/gltf-viewer/FILModelView.h @@ -16,6 +16,8 @@ #import +#include + namespace filament { class Engine; class Scene; @@ -76,6 +78,17 @@ typedef NSData* _Nonnull (^ResourceCallback)(NSString* _Nonnull); - (void)destroyModel; +/** + * Issues a pick query at the given view coordinates. + * The coordinates should be in UIKit's coordinate system, with the origin at + * the top-left. + * The callback is triggered with entity of the picked object. + */ +typedef void (^PickCallback)(utils::Entity); +- (void)issuePickQuery:(CGPoint)point callback:(PickCallback)callback; + +- (NSString* _Nullable)getEntityName:(utils::Entity)entity; + /** * Sets up a root transform on the current model to make it fit into a unit cube. */ diff --git a/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm b/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm index f2fa902fbf1..20448ee63cc 100644 --- a/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm +++ b/ios/samples/gltf-viewer/gltf-viewer/FILModelView.mm @@ -127,13 +127,12 @@ - (void)initCommon { _swapChain = _engine->createSwapChain((__bridge void*)self.layer); - _materialProvider = createUbershaderProvider(_engine, - UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE); + _materialProvider = + createUbershaderProvider(_engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE); EntityManager& em = EntityManager::get(); NameComponentManager* ncm = new NameComponentManager(em); _assetLoader = AssetLoader::create({_engine, _materialProvider, ncm, &em}); - _resourceLoader = new ResourceLoader( - {.engine = _engine, .normalizeSkinningWeights = true}); + _resourceLoader = new ResourceLoader({.engine = _engine, .normalizeSkinningWeights = true}); _stbDecoder = createStbProvider(_engine); _ktxDecoder = createKtx2Provider(_engine); _resourceLoader->addTextureProvider("image/png", _stbDecoder); @@ -182,6 +181,26 @@ - (void)destroyModel { _animator = nullptr; } +- (void)issuePickQuery:(CGPoint)point callback:(PickCallback)callback { + CGPoint pointOriginBottomLeft = CGPointMake(point.x, self.bounds.size.height - point.y); + CGPoint pointScaled = CGPointMake(pointOriginBottomLeft.x * self.contentScaleFactor, + pointOriginBottomLeft.y * self.contentScaleFactor); + _view->pick(pointScaled.x, pointScaled.y, + [callback](View::PickingQueryResult const& result) { callback(result.renderable); }); +} + +- (NSString* _Nullable)getEntityName:(utils::Entity)entity { + if (!_asset) { + return nil; + } + NameComponentManager* ncm = _assetLoader->getNames(); + NameComponentManager::Instance instance = ncm->getInstance(entity); + if (instance) { + return [NSString stringWithUTF8String:ncm->getName(ncm->getInstance(entity))]; + } + return nil; +} + - (void)transformToUnitCube { if (!_asset) { return; @@ -256,10 +275,6 @@ - (void)render { - (void)dealloc { [self destroyModel]; - delete _manipulator; - delete _stbDecoder; - delete _ktxDecoder; - _materialProvider->destroyMaterials(); delete _materialProvider; auto* ncm = _assetLoader->getNames(); @@ -267,6 +282,10 @@ - (void)dealloc { AssetLoader::destroy(&_assetLoader); delete _resourceLoader; + delete _manipulator; + delete _stbDecoder; + delete _ktxDecoder; + _engine->destroy(_swapChain); _engine->destroy(_view); EntityManager::get().destroy(_entities.camera); diff --git a/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm b/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm index 703ffa64167..80898ec19e1 100644 --- a/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm +++ b/ios/samples/gltf-viewer/gltf-viewer/FILViewController.mm @@ -15,6 +15,7 @@ */ #import "FILViewController.h" +#include #import "FILModelView.h" @@ -35,6 +36,9 @@ using namespace utils; using namespace ktxreader; +const float kToastAnimationDuration = 0.25f; +const float kToastDelayDuration = 2.0f; + @interface FILViewController () - (void)startDisplayLink; @@ -43,6 +47,9 @@ - (void)stopDisplayLink; - (void)createRenderables; - (void)createLights; +- (void)appWillResignActive:(NSNotification*)notification; +- (void)appDidBecomeActive:(NSNotification*)notification; + @end @implementation FILViewController { @@ -50,6 +57,7 @@ @implementation FILViewController { CFTimeInterval _startTime; viewer::RemoteServer* _server; viewer::AutomationEngine* _automation; + UILabel* _toastLabel; Texture* _skyboxTexture; Skybox* _skybox; @@ -57,6 +65,7 @@ @implementation FILViewController { IndirectLight* _indirectLight; Entity _sun; + UITapGestureRecognizer* _singleTapRecognizer; UITapGestureRecognizer* _doubleTapRecognizer; } @@ -67,6 +76,16 @@ - (void)viewDidLoad { self.title = @"https://google.github.io/filament/remote"; + // Observe lifecycle notifications to prevent us from rendering in the background. + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(appWillResignActive:) + name:UIApplicationWillResignActiveNotification + object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(appDidBecomeActive:) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + // Arguments: // --model // path to glb or gltf file to load from documents directory @@ -94,9 +113,37 @@ - (void)viewDidLoad { _server = new viewer::RemoteServer(); _automation = viewer::AutomationEngine::createDefault(); - _doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reloadModel)]; + _doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(reloadModel)]; _doubleTapRecognizer.numberOfTapsRequired = 2; [self.modelView addGestureRecognizer:_doubleTapRecognizer]; + _singleTapRecognizer = + [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(issuePickingQuery)]; + _singleTapRecognizer.numberOfTapsRequired = 1; + [self.modelView addGestureRecognizer:_singleTapRecognizer]; + + // Create a label at the top of the screen to toast messages to the user. + CGRect labelRect = self.view.bounds; + labelRect.size.height = 50; + _toastLabel = [[UILabel alloc] initWithFrame:labelRect]; + _toastLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _toastLabel.textAlignment = NSTextAlignmentCenter; + _toastLabel.textColor = [UIColor whiteColor]; + _toastLabel.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.5f]; + _toastLabel.numberOfLines = 0; + _toastLabel.lineBreakMode = NSLineBreakByWordWrapping; + _toastLabel.text = @""; + _toastLabel.alpha = 0.0f; + [self.view addSubview:_toastLabel]; +} + +- (void)appWillResignActive:(NSNotification*)notification { + [self stopDisplayLink]; +} + +- (void)appDidBecomeActive:(NSNotification*)notification { + [self startDisplayLink]; } - (void)viewWillAppear:(BOOL)animated { @@ -223,7 +270,8 @@ - (void)loadSettings:(viewer::ReceivedMessage const*)message { .indirectLight = _indirectLight, .sunlight = _sun, }; - _automation->applySettings(self.modelView.engine, message->buffer, message->bufferByteCount, content); + _automation->applySettings( + self.modelView.engine, message->buffer, message->bufferByteCount, content); ColorGrading* const colorGrading = _automation->getColorGrading(self.modelView.engine); self.modelView.view->setColorGrading(colorGrading); self.modelView.cameraFocalLength = _automation->getViewerOptions().cameraFocalLength; @@ -268,7 +316,42 @@ - (void)reloadModel { [self createDefaultRenderables]; } +- (void)issuePickingQuery { + CGPoint tapLocation = [_singleTapRecognizer locationInView:self.modelView]; + __weak typeof(self) weakSelf = self; + [self.modelView issuePickQuery:tapLocation + callback:^(utils::Entity entity) { + NSString* name = [self.modelView getEntityName:entity]; + if (!name) { + name = @""; + } + NSString* message = [NSString + stringWithFormat:@"Picked entity %d (%@) at (%d,%d)", + entity.getId(), name, int(tapLocation.x), int(tapLocation.y)]; + [weakSelf toastMessage:message]; + }]; +} + +- (void)toastMessage:(NSString*)message { + _toastLabel.text = message; + _toastLabel.alpha = 0.0f; + [UIView animateWithDuration:kToastAnimationDuration + animations:^{ + _toastLabel.alpha = 1.0f; + } + completion:^(BOOL finished) { + [UIView animateWithDuration:kToastAnimationDuration + delay:kToastDelayDuration + options:UIViewAnimationOptionCurveEaseInOut + animations:^{ + _toastLabel.alpha = 0.0f; + } + completion:nil]; + }]; +} + - (void)dealloc { + [NSNotificationCenter.defaultCenter removeObserver:self]; delete _server; delete _automation; self.modelView.engine->destroy(_indirectLight); diff --git a/ios/samples/transparent-rendering/transparent-rendering/Base.lproj/Main.storyboard b/ios/samples/transparent-rendering/transparent-rendering/Base.lproj/Main.storyboard index c676333761c..ebc26a4254a 100644 --- a/ios/samples/transparent-rendering/transparent-rendering/Base.lproj/Main.storyboard +++ b/ios/samples/transparent-rendering/transparent-rendering/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - - - - + + - + + + @@ -20,24 +20,24 @@ - - This is a native UITextView. The triangle is rendered by Filament. + This is a native UITextView. The triangle is rendered by Filament. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tincidunt neque a metus laoreet imperdiet. In cursus arcu quis turpis viverra convallis. Sed eu laoreet metus. Nunc id est lacus. Sed hendrerit elementum velit a finibus. Mauris vitae scelerisque erat. Proin ultricies quam nec justo vehicula, sed porta nulla facilisis. Phasellus orci lectus, ullamcorper efficitur urna non, sodales venenatis sem. Suspendisse auctor, enim non dapibus malesuada, est urna sollicitudin sapien, in sodales mi nulla quis odio. Suspendisse viverra vel orci vitae vestibulum. Fusce tristique ligula nisi, bibendum gravida est volutpat sed. Vivamus ut justo porttitor, feugiat lorem eget, rutrum orci. Donec nec nibh vitae ex consectetur pulvinar. Sed dignissim, metus a tincidunt finibus, justo massa auctor nunc, sit amet cursus ligula sapien ut enim. Quisque ut congue libero, in ultricies tortor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean ac justo in tortor commodo efficitur et ut leo. - +
    + - + - + @@ -45,4 +45,12 @@ Phasellus orci lectus, ullamcorper efficitur urna non, sodales venenatis sem. Su + + + + + + + + diff --git a/ios/samples/transparent-rendering/transparent-rendering/FilamentView.mm b/ios/samples/transparent-rendering/transparent-rendering/FilamentView.mm index fd5ff421371..9f858189537 100644 --- a/ios/samples/transparent-rendering/transparent-rendering/FilamentView.mm +++ b/ios/samples/transparent-rendering/transparent-rendering/FilamentView.mm @@ -89,6 +89,7 @@ - (instancetype)initWithCoder:(NSCoder*)coder #elif FILAMENT_APP_USE_METAL [self initializeMetalLayer]; #endif + self.opaque = NO; [self initializeFilament]; self.contentScaleFactor = UIScreen.mainScreen.nativeScale; } diff --git a/libs/camutils/src/FreeFlightManipulator.h b/libs/camutils/src/FreeFlightManipulator.h index 1df4dd56f73..c20b2578295 100644 --- a/libs/camutils/src/FreeFlightManipulator.h +++ b/libs/camutils/src/FreeFlightManipulator.h @@ -24,6 +24,7 @@ #include #include +#include #include namespace filament { @@ -121,50 +122,90 @@ class FreeFlightManipulator : public Manipulator { } void update(FLOAT deltaTime) override { - vec3 forceLocal { 0.0, 0.0, 0.0 }; - if (mKeyDown[(int) Base::Key::FORWARD]) { - forceLocal += vec3{ 0.0, 0.0, -1.0 }; - } - if (mKeyDown[(int) Base::Key::LEFT]) { - forceLocal += vec3{ -1.0, 0.0, 0.0 }; - } - if (mKeyDown[(int) Base::Key::BACKWARD]) { - forceLocal += vec3{ 0.0, 0.0, 1.0 }; - } - if (mKeyDown[(int) Base::Key::RIGHT]) { - forceLocal += vec3{ 1.0, 0.0, 0.0 }; - } - - const mat4 orientation = mat4::lookAt(Base::mEye, Base::mTarget, Base::mProps.upVector); - vec3 forceWorld = (orientation * vec4{ forceLocal, 0.0f }).xyz; - - if (mKeyDown[(int) Base::Key::UP]) { - forceWorld += vec3{ 0.0, 1.0, 0.0 }; - } - if (mKeyDown[(int) Base::Key::DOWN]) { - forceWorld += vec3{ 0.0, -1.0, 0.0 }; - } - - forceWorld *= mMoveSpeed; - - const auto dampingFactor = Base::mProps.flightMoveDamping; + auto getLocalDirection = [this]() -> vec3 { + vec3 directionLocal{ 0.0, 0.0, 0.0 }; + if (mKeyDown[(int)Base::Key::FORWARD]) { + directionLocal += vec3{ 0.0, 0.0, -1.0 }; + } + if (mKeyDown[(int)Base::Key::LEFT]) { + directionLocal += vec3{ -1.0, 0.0, 0.0 }; + } + if (mKeyDown[(int)Base::Key::BACKWARD]) { + directionLocal += vec3{ 0.0, 0.0, 1.0 }; + } + if (mKeyDown[(int)Base::Key::RIGHT]) { + directionLocal += vec3{ 1.0, 0.0, 0.0 }; + } + return directionLocal; + }; + + auto getWorldDirection = [this](vec3 directionLocal) -> vec3 { + const mat4 orientation = mat4::lookAt(Base::mEye, Base::mTarget, Base::mProps.upVector); + vec3 directionWorld = (orientation * vec4{ directionLocal, 0.0f }).xyz; + if (mKeyDown[(int)Base::Key::UP]) { + directionWorld += vec3{ 0.0, 1.0, 0.0 }; + } + if (mKeyDown[(int)Base::Key::DOWN]) { + directionWorld += vec3{ 0.0, -1.0, 0.0 }; + } + return directionWorld; + }; + + vec3 const localDirection = getLocalDirection(); + vec3 const worldDirection = getWorldDirection(localDirection); + + // unit of dampingFactor is [1/s] + FLOAT const dampingFactor = Base::mProps.flightMoveDamping; if (dampingFactor == 0.0) { // Without damping, we simply treat the force as our velocity. - mEyeVelocity = forceWorld; + vec3 const speed = worldDirection * mMoveSpeed; + mEyeVelocity = speed; + vec3 const positionDelta = mEyeVelocity * deltaTime; + Base::mEye += positionDelta; + Base::mTarget += positionDelta; } else { - // The dampingFactor acts as "friction", which acts upon the camera in the direction - // opposite its velocity. - // Force is also multiplied by the dampingFactor, to "make up" for the friction. - // This ensures that the max velocity still approaches mMoveSpeed; - vec3 velocityDelta = (forceWorld - mEyeVelocity) * dampingFactor; - mEyeVelocity += velocityDelta * deltaTime; + auto dt = deltaTime / 16.0; + for (size_t i = 0; i < 16; i++) { + // Note: the algorithm below doesn't work well for large time steps because + // we're not using a closed form for updating the position, so we need + // to loop a few times. We could make this better by having a dynamic + // loop count. What we're really doing is evaluation the solution to + // a differential equation numerically. + + // Kinetic friction is a force opposing velocity and proportional to it.: + // F = -kv + // F = ma + // ==> ma = -kv + // a = -vk/m [m.s^-2] = [m/s] * [Kg/s] / [Kg] + // ==> dampingFactor = k/m [1/s] = [Kg/s] / [Kg] + // + // The velocity update for dt due to friction is then: + // v = v + a.dt + // = v - v * dampingFactor * dt + // = v * (1.0 - dampingFactor * dt) + mEyeVelocity = mEyeVelocity * saturate(1.0 - dampingFactor * dt); + + // We also undergo an acceleration proportional to the distance to the target speed + // (the closer we are the less we accelerate, similar to a car). + // F = k * (target_v - v) + // F = ma + // ==> ma = k * (target_v - v) + // a = k/m * (target_v - v) [m.s^-2] = [Kg/s] / [Kg] * [m/s] + // + // The velocity update for dt due to the acceleration (the gas basically) is then: + // v = v + a.dt + // = v + k/m * (target_v - v).dt + // We're using the same dampingFactor here, but we don't have to. + auto const accelerationFactor = dampingFactor; + vec3 const acceleration = worldDirection * + (accelerationFactor * std::max(mMoveSpeed - length(mEyeVelocity), FLOAT(0))); + mEyeVelocity += acceleration * dt; + vec3 const positionDelta = mEyeVelocity * dt; + Base::mEye += positionDelta; + Base::mTarget += positionDelta; + } } - - const vec3 positionDelta = mEyeVelocity * deltaTime; - - Base::mEye += positionDelta; - Base::mTarget += positionDelta; } Bookmark getCurrentBookmark() const override { diff --git a/libs/filabridge/include/filament/MaterialChunkType.h b/libs/filabridge/include/filament/MaterialChunkType.h index 6cff20039a1..27d50c1683a 100644 --- a/libs/filabridge/include/filament/MaterialChunkType.h +++ b/libs/filabridge/include/filament/MaterialChunkType.h @@ -59,6 +59,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialFeatureLevel = charTo64bitNum("MAT_FEAT"), MaterialShading = charTo64bitNum("MAT_SHAD"), MaterialBlendingMode = charTo64bitNum("MAT_BLEN"), + MaterialBlendFunction = charTo64bitNum("MAT_BLFN"), MaterialTransparencyMode = charTo64bitNum("MAT_TRMD"), MaterialMaskThreshold = charTo64bitNum("MAT_THRS"), MaterialShadowMultiplier = charTo64bitNum("MAT_SHML"), diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index 75bcfcf7140..9f348481895 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -28,7 +28,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 44; +static constexpr size_t MATERIAL_VERSION = 51; /** * Supported shading models @@ -80,6 +80,8 @@ enum class BlendingMode : uint8_t { MULTIPLY, //! material brightens what's behind it SCREEN, + //! custom blending function + CUSTOM, }; /** @@ -201,7 +203,7 @@ enum class ReflectionMode : uint8_t { // can't really use std::underlying_type::type because the driver takes a uint32_t using AttributeBitset = utils::bitset32; -static constexpr size_t MATERIAL_PROPERTIES_COUNT = 26; +static constexpr size_t MATERIAL_PROPERTIES_COUNT = 27; enum class Property : uint8_t { BASE_COLOR, //!< float4, all shading models ROUGHNESS, //!< float, lit shading models only @@ -223,6 +225,7 @@ enum class Property : uint8_t { EMISSIVE, //!< float4, all shading models NORMAL, //!< float3, all shading models only, except unlit POST_LIGHTING_COLOR, //!< float4, all shading models + POST_LIGHTING_MIX_FACTOR,//!< float, all shading models CLIP_SPACE_TRANSFORM, //!< mat4, vertex shader only ABSORPTION, //!< float3, how much light is absorbed by the material TRANSMISSION, //!< float, how much light is refracted through the material diff --git a/libs/filabridge/include/private/filament/BufferInterfaceBlock.h b/libs/filabridge/include/private/filament/BufferInterfaceBlock.h index 2d7c8963de3..c47193208fd 100644 --- a/libs/filabridge/include/private/filament/BufferInterfaceBlock.h +++ b/libs/filabridge/include/private/filament/BufferInterfaceBlock.h @@ -160,6 +160,8 @@ class BufferInterfaceBlock { bool isEmpty() const noexcept { return mFieldInfoList.empty(); } + bool isEmptyForFeatureLevel(backend::FeatureLevel featureLevel) const noexcept; + Alignment getAlignment() const noexcept { return mAlignment; } Target getTarget() const noexcept { return mTarget; } diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index fb059d2e89f..30b2f0663c6 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -26,7 +26,9 @@ namespace filament { -static constexpr size_t POST_PROCESS_VARIANT_COUNT = 2; +static constexpr size_t POST_PROCESS_VARIANT_BITS = 1; +static constexpr size_t POST_PROCESS_VARIANT_COUNT = (1u << POST_PROCESS_VARIANT_BITS); +static constexpr size_t POST_PROCESS_VARIANT_MASK = POST_PROCESS_VARIANT_COUNT - 1; enum class PostProcessVariant : uint8_t { OPAQUE, TRANSLUCENT @@ -65,6 +67,8 @@ enum class ReservedSpecializationConstants : uint8_t { CONFIG_FROXEL_BUFFER_HEIGHT = 4, CONFIG_POWER_VR_SHADER_WORKAROUNDS = 5, CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP = 6, + CONFIG_DEBUG_FROXEL_VISUALIZATION = 7, + CONFIG_STEREO_EYE_COUNT = 8, // don't change (hardcoded in ShaderCompilerService.cpp) }; // This value is limited by UBO size, ES3.0 only guarantees 16 KiB. @@ -74,7 +78,8 @@ constexpr size_t CONFIG_MAX_LIGHT_INDEX = CONFIG_MAX_LIGHT_COUNT - 1; // The number of specialization constants that Filament reserves for its own use. These are always // the first constants (from 0 to CONFIG_MAX_RESERVED_SPEC_CONSTANTS - 1). -constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 8; +// Updating this value necessitates a material version bump. +constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 16; // The maximum number of shadowmaps. // There is currently a maximum limit of 128 shadowmaps. @@ -119,8 +124,10 @@ constexpr size_t CONFIG_MAX_BONE_COUNT = 256; // Furthermore, this is constrained by CONFIG_MINSPEC_UBO_SIZE (16 bytes per morph target). constexpr size_t CONFIG_MAX_MORPH_TARGET_COUNT = 256; -// The number of eyes in stereoscopic mode. -constexpr uint8_t CONFIG_STEREOSCOPIC_EYES = 2; +// The max number of eyes supported in stereoscopic mode. +// The number of eyes actually rendered is set at Engine creation time, see +// Engine::Config::stereoscopicEyeCount. +constexpr uint8_t CONFIG_MAX_STEREOSCOPIC_EYES = 4; } // namespace filament @@ -130,11 +137,15 @@ template<> struct utils::EnableIntegerOperators : public std::true_type {}; template<> struct utils::EnableIntegerOperators : public std::true_type {}; +template<> +struct utils::EnableIntegerOperators : public std::true_type {}; template<> inline constexpr size_t utils::Enum::count() { return 9; } template<> inline constexpr size_t utils::Enum::count() { return 4; } +template<> +inline constexpr size_t utils::Enum::count() { return filament::POST_PROCESS_VARIANT_COUNT; } static_assert(utils::Enum::count() <= filament::backend::CONFIG_UNIFORM_BINDING_COUNT); static_assert(utils::Enum::count() <= filament::backend::CONFIG_SAMPLER_BINDING_COUNT); diff --git a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h index af592155a66..03f5b5e82cb 100644 --- a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h +++ b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h @@ -20,13 +20,16 @@ #include -#include #include #include #include #include #include +#include + +#include +#include namespace filament { diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index 1def148334e..6a6f14ece16 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -76,7 +76,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) math::mat4f worldFromViewMatrix; // clip view -> world : model matrix math::mat4f clipFromViewMatrix; // clip <- view world : projection matrix math::mat4f viewFromClipMatrix; // clip -> view world : inverse projection matrix - math::mat4f clipFromWorldMatrix[CONFIG_STEREOSCOPIC_EYES]; // clip <- view <- world + math::mat4f clipFromWorldMatrix[CONFIG_MAX_STEREOSCOPIC_EYES]; // clip <- view <- world math::mat4f worldFromClipMatrix; // clip -> view -> world math::mat4f userWorldFromWorldMatrix; // userWorld <- world math::float4 clipTransform; // [sx, sy, tx, ty] only used by VERTEX_DOMAIN_DEVICE @@ -99,6 +99,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float lodBias; // load bias to apply to user materials float refractionLodOffset; + math::float2 derivativesScale; // camera position in view space (when camera_at_origin is enabled), i.e. it's (0,0,0). float oneOverFarMinusNear; // 1 / (f-n), always positive @@ -111,8 +112,6 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) // AO float aoSamplingQualityAndEdgeDistance; // <0: no AO, 0: bilinear, !0: bilateral edge distance float aoBentNormals; // 0: no AO bent normal, >0.0 AO bent normals - float aoReserved0; - float aoReserved1; // -------------------------------------------------------------------------------------------- // Dynamic Lighting [variant: DYN] @@ -203,7 +202,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float es2Reserved2; // bring PerViewUib to 2 KiB - math::float4 reserved[48]; + math::float4 reserved[40]; }; // 2 KiB == 128 float4s diff --git a/libs/filabridge/include/private/filament/Variant.h b/libs/filabridge/include/private/filament/Variant.h index 2c3af27e4cc..fc94dec7f6a 100644 --- a/libs/filabridge/include/private/filament/Variant.h +++ b/libs/filabridge/include/private/filament/Variant.h @@ -115,7 +115,7 @@ struct Variant { inline bool hasDirectionalLighting() const noexcept { return key & DIR; } inline bool hasDynamicLighting() const noexcept { return key & DYN; } inline bool hasSkinningOrMorphing() const noexcept { return key & SKN; } - inline bool hasInstancedStereo() const noexcept { return key & STE; } + inline bool hasStereo() const noexcept { return key & STE; } inline void setDirectionalLighting(bool v) noexcept { set(v, DIR); } inline void setDynamicLighting(bool v) noexcept { set(v, DYN); } diff --git a/libs/filabridge/src/BufferInterfaceBlock.cpp b/libs/filabridge/src/BufferInterfaceBlock.cpp index 96e725f95fe..6c6cf9efc5c 100644 --- a/libs/filabridge/src/BufferInterfaceBlock.cpp +++ b/libs/filabridge/src/BufferInterfaceBlock.cpp @@ -171,6 +171,14 @@ BufferInterfaceBlock::FieldInfo const* BufferInterfaceBlock::getFieldInfo( return &mFieldInfoList[pos->second]; } +bool BufferInterfaceBlock::isEmptyForFeatureLevel( + backend::FeatureLevel featureLevel) const noexcept { + return std::all_of(mFieldInfoList.begin(), mFieldInfoList.end(), + [featureLevel](auto const &info) { + return featureLevel < info.minFeatureLevel; + }); +} + uint8_t UTILS_NOINLINE BufferInterfaceBlock::baseAlignmentForType(BufferInterfaceBlock::Type type) noexcept { switch (type) { case Type::BOOL: @@ -230,4 +238,3 @@ uint8_t UTILS_NOINLINE BufferInterfaceBlock::strideForType(BufferInterfaceBlock: } } // namespace filament - diff --git a/libs/filagui/CMakeLists.txt b/libs/filagui/CMakeLists.txt index e68a511ec2a..66058c4103c 100644 --- a/libs/filagui/CMakeLists.txt +++ b/libs/filagui/CMakeLists.txt @@ -39,6 +39,7 @@ 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} diff --git a/libs/filagui/src/materials/uiBlit.mat b/libs/filagui/src/materials/uiBlit.mat index 46b66338a91..08b2161b13e 100644 --- a/libs/filagui/src/materials/uiBlit.mat +++ b/libs/filagui/src/materials/uiBlit.mat @@ -13,7 +13,8 @@ material { shadingModel : unlit, culling : none, depthCulling: false, - blending : transparent + blending : transparent, + featureLevel : 0 } fragment { @@ -21,7 +22,7 @@ fragment { prepareMaterial(material); vec2 uv = getUV0(); uv.y = 1.0 - uv.y; - vec4 albedo = texture(materialParams_albedo, uv); + vec4 albedo = texture2D(materialParams_albedo, uv); material.baseColor = getColor() * albedo; material.baseColor.rgb *= material.baseColor.a; } diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index 36c24c4161c..4b66965d746 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -135,6 +135,7 @@ class UTILS_PUBLIC MaterialBuilderBase { Optimization mOptimization = Optimization::PERFORMANCE; bool mPrintShaders = false; bool mGenerateDebugInfo = false; + bool mIncludeEssl1 = true; utils::bitset32 mShaderModels; struct CodeGenParams { ShaderModel shaderModel; @@ -226,6 +227,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using ShaderQuality = filament::ShaderQuality; using BlendingMode = filament::BlendingMode; + using BlendFunction = filament::backend::BlendFunction; using Shading = filament::Shading; using Interpolation = filament::Interpolation; using VertexDomain = filament::VertexDomain; @@ -242,6 +244,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using Precision = filament::backend::Precision; using CullingMode = filament::backend::CullingMode; using FeatureLevel = filament::backend::FeatureLevel; + using StereoscopicType = filament::backend::StereoscopicType; enum class VariableQualifier : uint8_t { OUT @@ -271,6 +274,9 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { MaterialBuilder& noSamplerValidation(bool enabled) noexcept; + //! Enable generation of ESSL 1.0 code in FL0 materials. + MaterialBuilder& includeEssl1(bool enabled) noexcept; + //! Set the name of this material. MaterialBuilder& name(const char* name) noexcept; @@ -307,12 +313,8 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { */ MaterialBuilder& parameter(const char* name, SamplerType samplerType, SamplerFormat format = SamplerFormat::FLOAT, - ParameterPrecision precision = ParameterPrecision::DEFAULT) noexcept; - - /// @copydoc parameter(SamplerType, SamplerFormat, ParameterPrecision, const char*) - MaterialBuilder& parameter(const char* name, SamplerType samplerType, - ParameterPrecision precision) noexcept; - + ParameterPrecision precision = ParameterPrecision::DEFAULT, + bool multisample = false) noexcept; MaterialBuilder& buffer(filament::BufferInterfaceBlock bib) noexcept; @@ -406,10 +408,20 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { */ MaterialBuilder& blending(BlendingMode blending) noexcept; + /** + * Set the blend function for this material. blending must be et to CUSTOM. + */ + MaterialBuilder& customBlendFunctions( + BlendFunction srcRGB, + BlendFunction srcA, + BlendFunction dstRGB, + BlendFunction dstA) noexcept; + /** * Set the blending mode of the post-lighting color for this material. * Only OPAQUE, TRANSPARENT and ADD are supported, the default is TRANSPARENT. - * This setting requires the material property "postLightingColor" to be set. + * This setting requires the material properties "postLightingColor" and + * "postLightingMixFactor" to be set. */ MaterialBuilder& postLightingBlending(BlendingMode blending) noexcept; @@ -521,6 +533,12 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Specifies how transparent objects should be rendered (default is DEFAULT). MaterialBuilder& transparencyMode(TransparencyMode mode) noexcept; + //! Specify the stereoscopic type (default is INSTANCED) + MaterialBuilder& stereoscopicType(StereoscopicType stereoscopicType) noexcept; + + //! Specify the number of eyes for stereoscopic rendering + MaterialBuilder& stereoscopicEyeCount(uint8_t eyeCount) noexcept; + /** * Enable / disable custom surface shading. Custom surface shading requires the LIT * shading model. In addition, the following function must be defined in the fragment @@ -618,8 +636,8 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { Parameter() noexcept: parameterType(INVALID) {} // Sampler - Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p) - : name(paramName), size(1), precision(p), samplerType(t), format(f), parameterType(SAMPLER) { } + Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p, bool ms) + : name(paramName), size(1), precision(p), samplerType(t), format(f), parameterType(SAMPLER), multisample(ms) { } // Uniform Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p) @@ -636,6 +654,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { SamplerType samplerType; SubpassType subpassType; SamplerFormat format; + bool multisample; enum { INVALID, UNIFORM, @@ -819,6 +838,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; BlendingMode mBlendingMode = BlendingMode::OPAQUE; BlendingMode mPostLightingBlendingMode = BlendingMode::TRANSPARENT; + std::array mCustomBlendFunctions = {}; CullingMode mCullingMode = CullingMode::BACK; Shading mShading = Shading::LIT; MaterialDomain mMaterialDomain = MaterialDomain::SURFACE; @@ -828,6 +848,8 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { Interpolation mInterpolation = Interpolation::SMOOTH; VertexDomain mVertexDomain = VertexDomain::OBJECT; TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; + StereoscopicType mStereoscopicType = StereoscopicType::INSTANCED; + uint8_t mStereoscopicEyeCount = 2; filament::AttributeBitset mRequiredAttributes; diff --git a/libs/filamat/src/Enums.cpp b/libs/filamat/src/Enums.cpp index 01c4011c7ef..583d376f951 100644 --- a/libs/filamat/src/Enums.cpp +++ b/libs/filamat/src/Enums.cpp @@ -21,32 +21,33 @@ namespace filamat { std::unordered_map Enums::mStringToProperty = { - { "baseColor", Property::BASE_COLOR }, - { "roughness", Property::ROUGHNESS }, - { "metallic", Property::METALLIC }, - { "reflectance", Property::REFLECTANCE }, - { "ambientOcclusion", Property::AMBIENT_OCCLUSION }, - { "clearCoat", Property::CLEAR_COAT }, - { "clearCoatRoughness", Property::CLEAR_COAT_ROUGHNESS }, - { "clearCoatNormal", Property::CLEAR_COAT_NORMAL }, - { "anisotropy", Property::ANISOTROPY }, - { "anisotropyDirection", Property::ANISOTROPY_DIRECTION }, - { "thickness", Property::THICKNESS }, - { "subsurfacePower", Property::SUBSURFACE_POWER }, - { "subsurfaceColor", Property::SUBSURFACE_COLOR }, - { "sheenColor", Property::SHEEN_COLOR }, - { "sheenRoughness", Property::SHEEN_ROUGHNESS }, - { "glossiness", Property::GLOSSINESS }, - { "specularColor", Property::SPECULAR_COLOR }, - { "emissive", Property::EMISSIVE }, - { "normal", Property::NORMAL }, - { "postLightingColor", Property::POST_LIGHTING_COLOR }, - { "clipSpaceTransform", Property::CLIP_SPACE_TRANSFORM }, - { "absorption", Property::ABSORPTION }, - { "transmission", Property::TRANSMISSION }, - { "ior", Property::IOR }, - { "microThickness", Property::MICRO_THICKNESS }, - { "bentNormal", Property::BENT_NORMAL }, + { "baseColor", Property::BASE_COLOR }, + { "roughness", Property::ROUGHNESS }, + { "metallic", Property::METALLIC }, + { "reflectance", Property::REFLECTANCE }, + { "ambientOcclusion", Property::AMBIENT_OCCLUSION }, + { "clearCoat", Property::CLEAR_COAT }, + { "clearCoatRoughness", Property::CLEAR_COAT_ROUGHNESS }, + { "clearCoatNormal", Property::CLEAR_COAT_NORMAL }, + { "anisotropy", Property::ANISOTROPY }, + { "anisotropyDirection", Property::ANISOTROPY_DIRECTION }, + { "thickness", Property::THICKNESS }, + { "subsurfacePower", Property::SUBSURFACE_POWER }, + { "subsurfaceColor", Property::SUBSURFACE_COLOR }, + { "sheenColor", Property::SHEEN_COLOR }, + { "sheenRoughness", Property::SHEEN_ROUGHNESS }, + { "glossiness", Property::GLOSSINESS }, + { "specularColor", Property::SPECULAR_COLOR }, + { "emissive", Property::EMISSIVE }, + { "normal", Property::NORMAL }, + { "postLightingColor", Property::POST_LIGHTING_COLOR }, + { "postLightingMixFactor", Property::POST_LIGHTING_MIX_FACTOR }, + { "clipSpaceTransform", Property::CLIP_SPACE_TRANSFORM }, + { "absorption", Property::ABSORPTION }, + { "transmission", Property::TRANSMISSION }, + { "ior", Property::IOR }, + { "microThickness", Property::MICRO_THICKNESS }, + { "bentNormal", Property::BENT_NORMAL }, }; template <> diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index db36aae23d9..ce8db716dfc 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -23,6 +23,7 @@ #include #include +#include "backend/DriverEnums.h" #include "sca/builtinResource.h" #include "sca/GLSLTools.h" @@ -32,6 +33,7 @@ #include "MetalArgumentBuffer.h" #include "SpirvFixup.h" +#include "utils/ostream.h" #include @@ -167,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. @@ -232,7 +235,7 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl, for (const auto& info: infoList) { const std::string name = info.uniformName.c_str(); argBufferBuilder - .texture(info.offset * 2, name, info.type, info.format) + .texture(info.offset * 2, name, info.type, info.format, info.multisample) .sampler(info.offset * 2 + 1, name + "Smplr"); } @@ -395,7 +398,9 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co break; case MaterialBuilder::Optimization::SIZE: case MaterialBuilder::Optimization::PERFORMANCE: - fullOptimization(tShader, config, internalConfig); + if (!fullOptimization(tShader, config, internalConfig)) { + return false; + } break; } @@ -478,7 +483,7 @@ void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader, } } -void GLSLPostProcessor::fullOptimization(const TShader& tShader, +bool GLSLPostProcessor::fullOptimization(const TShader& tShader, GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const { SpirvBlob spirv; @@ -532,6 +537,20 @@ void GLSLPostProcessor::fullOptimization(const TShader& tShader, glslOptions.emit_uniform_buffer_as_plain_uniforms = true; } + if (config.variant.hasStereo() && config.shaderType == ShaderStage::VERTEX) { + switch (config.materialInfo->stereoscopicType) { + case StereoscopicType::INSTANCED: + // Nothing to generate + break; + case StereoscopicType::MULTIVIEW: + // For stereo variants using multiview feature, this generates the shader code below. + // #extension GL_OVR_multiview2 : require + // layout(num_views = 2) in; + glslOptions.ovr_multiview_view_count = config.materialInfo->stereoscopicEyeCount; + break; + } + } + CompilerGLSL glslCompiler(std::move(spirv)); glslCompiler.set_common_options(glslOptions); @@ -546,19 +565,29 @@ void GLSLPostProcessor::fullOptimization(const TShader& tShader, } } +#ifdef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS *internalConfig.glslOutput = glslCompiler.compile(); +#else + try { + *internalConfig.glslOutput = glslCompiler.compile(); + } catch (spirv_cross::CompilerError e) { + slog.e << "ERROR: " << e.what() << io::endl; + return false; + } +#endif // spirv-cross automatically redeclares gl_ClipDistance if it's used. Some drivers don't // like this, so we simply remove it. // According to EXT_clip_cull_distance, gl_ClipDistance can be // "implicitly sized by indexing it only with integral constant expressions". std::string& str = *internalConfig.glslOutput; - const std::string clipDistanceDefinition = "out float gl_ClipDistance[1];"; + const std::string clipDistanceDefinition = "out float gl_ClipDistance[2];"; size_t const found = str.find(clipDistanceDefinition); if (found != std::string::npos) { str.replace(found, clipDistanceDefinition.length(), ""); } } + return true; } std::shared_ptr GLSLPostProcessor::createOptimizer( diff --git a/libs/filamat/src/GLSLPostProcessor.h b/libs/filamat/src/GLSLPostProcessor.h index 8e3c2e1e4ac..c13dece6369 100644 --- a/libs/filamat/src/GLSLPostProcessor.h +++ b/libs/filamat/src/GLSLPostProcessor.h @@ -93,7 +93,7 @@ class GLSLPostProcessor { ShaderMinifier minifier; }; - void fullOptimization(const glslang::TShader& tShader, + bool fullOptimization(const glslang::TShader& tShader, GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const; void preprocessOptimization(glslang::TShader& tShader, diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 01e645c6877..109d6e43da5 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -139,13 +139,13 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, glTargetLanguage, effectiveFeatureLevel, }); - if (featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0 - && shaderModel == ShaderModel::MOBILE) { - // ESSL1 code may never be compiled to SPIR-V. + if (mIncludeEssl1 + && featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0 + && shaderModel == ShaderModel::MOBILE) { mCodeGenPermutations.push_back({ shaderModel, TargetApi::OPENGL, - TargetLanguage::GLSL, + glTargetLanguage, filament::backend::FeatureLevel::FEATURE_LEVEL_0 }); } @@ -250,17 +250,20 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType type, MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType, - SamplerFormat format, ParameterPrecision precision) noexcept { + SamplerFormat format, ParameterPrecision precision, bool multisample) noexcept { + + ASSERT_PRECONDITION( + !multisample || (format != SamplerFormat::SHADOW && ( + samplerType == SamplerType::SAMPLER_2D + || samplerType == SamplerType::SAMPLER_2D_ARRAY)), + "multisample samplers only possible with SAMPLER_2D or SAMPLER_2D_ARRAY," + " as long as type is not SHADOW"); + ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters"); - mParameters[mParameterCount++] = { name, samplerType, format, precision }; + mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample }; return *this; } -MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType, - ParameterPrecision precision) noexcept { - return parameter(name, samplerType, SamplerFormat::FLOAT, precision); -} - template MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType type, T defaultValue) { auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) { @@ -385,6 +388,16 @@ MaterialBuilder& MaterialBuilder::blending(BlendingMode blending) noexcept { return *this; } +MaterialBuilder& MaterialBuilder::customBlendFunctions( + BlendFunction srcRGB, BlendFunction srcA, + BlendFunction dstRGB, BlendFunction dstA) noexcept { + mCustomBlendFunctions[0] = srcRGB; + mCustomBlendFunctions[1] = srcA; + mCustomBlendFunctions[2] = dstRGB; + mCustomBlendFunctions[3] = dstA; + return *this; +} + MaterialBuilder& MaterialBuilder::postLightingBlending(BlendingMode blending) noexcept { mPostLightingBlendingMode = blending; return *this; @@ -495,6 +508,16 @@ MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode mode) noexce return *this; } +MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType stereoscopicType) noexcept { + mStereoscopicType = stereoscopicType; + return *this; +} + +MaterialBuilder& MaterialBuilder::stereoscopicEyeCount(uint8_t eyeCount) noexcept { + mStereoscopicEyeCount = eyeCount; + return *this; +} + MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode mode) noexcept { mReflectionMode = mode; return *this; @@ -556,7 +579,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { assert_invariant(!param.isSubpass()); if (param.isSampler()) { sbb.add({ param.name.data(), param.name.size() }, - param.samplerType, param.format, param.precision); + param.samplerType, param.format, param.precision, param.multisample); } else if (param.isUniform()) { ibb.add({{{ param.name.data(), param.name.size() }, uint32_t(param.size == 1u ? 0u : param.size), param.uniformType, @@ -587,11 +610,11 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { } if (mBlendingMode == BlendingMode::MASKED) { - ibb.add({{ "_maskThreshold", 0, UniformType::FLOAT }}); + ibb.add({{ "_maskThreshold", 0, UniformType::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }}); } if (mDoubleSidedCapability) { - ibb.add({{ "_doubleSided", 0, UniformType::BOOL }}); + ibb.add({{ "_doubleSided", 0, UniformType::BOOL, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }}); } mRequiredAttributes.set(VertexAttribute::POSITION); @@ -629,6 +652,11 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { info.vertexDomainDeviceJittered = mVertexDomainDeviceJittered; info.featureLevel = mFeatureLevel; info.groupSize = mGroupSize; + info.stereoscopicType = mStereoscopicType; + info.stereoscopicEyeCount = mStereoscopicEyeCount; + + // This is determined via static analysis of the glsl after prepareToBuild(). + info.userMaterialHasCustomDepth = false; } bool MaterialBuilder::findProperties(backend::ShaderStage type, @@ -888,7 +916,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector(ChunkType::MaterialDoubleSided, mDoubleSided); container.emplace(ChunkType::MaterialBlendingMode, static_cast(mBlendingMode)); + + if (mBlendingMode == BlendingMode::CUSTOM) { + uint32_t const blendFunctions = + (uint32_t(mCustomBlendFunctions[0]) << 24) | + (uint32_t(mCustomBlendFunctions[1]) << 16) | + (uint32_t(mCustomBlendFunctions[2]) << 8) | + (uint32_t(mCustomBlendFunctions[3]) << 0); + container.emplace< uint32_t >(ChunkType::MaterialBlendFunction, blendFunctions); + } + container.emplace(ChunkType::MaterialTransparencyMode, static_cast(mTransparencyMode)); container.emplace(ChunkType::MaterialReflectionMode, @@ -1556,4 +1599,9 @@ MaterialBuilder& MaterialBuilder::noSamplerValidation(bool enabled) noexcept { return *this; } +MaterialBuilder& MaterialBuilder::includeEssl1(bool enabled) noexcept { + mIncludeEssl1 = enabled; + return *this; +} + } // namespace filamat diff --git a/libs/filamat/src/MetalArgumentBuffer.cpp b/libs/filamat/src/MetalArgumentBuffer.cpp index 436680ae295..506cb8e480a 100644 --- a/libs/filamat/src/MetalArgumentBuffer.cpp +++ b/libs/filamat/src/MetalArgumentBuffer.cpp @@ -29,11 +29,20 @@ MetalArgumentBuffer::Builder& filamat::MetalArgumentBuffer::Builder::name( MetalArgumentBuffer::Builder& MetalArgumentBuffer::Builder::texture(size_t index, const std::string& name, filament::backend::SamplerType type, - filament::backend::SamplerFormat format) noexcept { + filament::backend::SamplerFormat format, + bool multisample) noexcept { + + using namespace filament::backend; + // All combinations of SamplerType and SamplerFormat are valid except for SAMPLER_3D / SHADOW. - assert_invariant(type != filament::backend::SamplerType::SAMPLER_3D || - format != filament::backend::SamplerFormat::SHADOW); - mArguments.emplace_back(TextureArgument { name, index, type, format }); + assert_invariant(type != SamplerType::SAMPLER_3D || format != SamplerFormat::SHADOW); + + // multisample textures have restrictions too + assert_invariant(!multisample || ( + format != SamplerFormat::SHADOW && ( + type == SamplerType::SAMPLER_2D || type == SamplerType::SAMPLER_2D_ARRAY))); + + mArguments.emplace_back(TextureArgument { name, index, type, format, multisample }); return *this; } @@ -79,6 +88,10 @@ std::ostream& MetalArgumentBuffer::Builder::TextureArgument::write(std::ostream& break; } + if (multisample) { + os << "_ms"; + } + switch (format) { case filament::backend::SamplerFormat::INT: os << ""; diff --git a/libs/filamat/src/MetalArgumentBuffer.h b/libs/filamat/src/MetalArgumentBuffer.h index f04d9e059d7..608645fca12 100644 --- a/libs/filamat/src/MetalArgumentBuffer.h +++ b/libs/filamat/src/MetalArgumentBuffer.h @@ -48,7 +48,8 @@ class MetalArgumentBuffer { */ Builder& texture(size_t index, const std::string& name, filament::backend::SamplerType type, - filament::backend::SamplerFormat format) noexcept; + filament::backend::SamplerFormat format, + bool multisample) noexcept; /** * Add a sampler argument to the argument buffer structure. @@ -69,6 +70,7 @@ class MetalArgumentBuffer { size_t index; filament::backend::SamplerType type; filament::backend::SamplerFormat format; + bool multisample; std::ostream& write(std::ostream& os) const; }; diff --git a/libs/filamat/src/eiff/Flattener.h b/libs/filamat/src/eiff/Flattener.h index a658afe6e04..2e3cfa22352 100644 --- a/libs/filamat/src/eiff/Flattener.h +++ b/libs/filamat/src/eiff/Flattener.h @@ -17,13 +17,19 @@ #ifndef TNT_FILAMAT_FLATENNER_H #define TNT_FILAMAT_FLATENNER_H -#include +#include +#include +#include +#include #include +#include #include #include #include +#include +#include #include using namespace utils; @@ -97,7 +103,7 @@ class Flattener { } void writeString(const char* str) { - size_t len = strlen(str); + size_t const len = strlen(str); if (mStart != nullptr) { strcpy(reinterpret_cast(mCursor), str); } @@ -105,7 +111,7 @@ class Flattener { } void writeString(std::string_view str) { - size_t len = str.length(); + size_t const len = str.length(); if (mStart != nullptr) { memcpy(reinterpret_cast(mCursor), str.data(), len); mCursor[len] = 0; @@ -145,12 +151,12 @@ class Flattener { } uint32_t writeSize() { - assert(mSizePlaceholders.size() > 0); + assert(!mSizePlaceholders.empty()); uint8_t* dst = mSizePlaceholders.back(); mSizePlaceholders.pop_back(); // -4 to account for the 4 bytes we are about to write. - uint32_t size = static_cast(mCursor - dst - 4); + uint32_t const size = static_cast(mCursor - dst - 4); if (mStart != nullptr) { dst[0] = static_cast( size & 0xff); dst[1] = static_cast((size >> 8) & 0xff); @@ -177,12 +183,12 @@ class Flattener { } for(auto pair : mOffsetPlaceholders) { - size_t index = pair.first; + size_t const index = pair.first; if (index != forIndex) { continue; } uint8_t* dst = pair.second; - size_t offset = mCursor - mOffsetsBase; + size_t const offset = mCursor - mOffsetsBase; if (offset > UINT32_MAX) { slog.e << "Unable to write offset greater than UINT32_MAX." << io::endl; exit(0); @@ -206,7 +212,7 @@ class Flattener { } void writePlaceHoldValue(size_t v) { - assert(mValuePlaceholders.size() > 0); + assert(!mValuePlaceholders.empty()); if (v > UINT32_MAX) { slog.e << "Unable to write value greater than UINT32_MAX." << io::endl; diff --git a/libs/filamat/src/sca/GLSLTools.cpp b/libs/filamat/src/sca/GLSLTools.cpp index 91f885d9c30..eeecf89efba 100644 --- a/libs/filamat/src/sca/GLSLTools.cpp +++ b/libs/filamat/src/sca/GLSLTools.cpp @@ -176,7 +176,7 @@ bool GLSLTools::analyzeComputeShader(const std::string& shaderCode, bool const ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg); if (!ok) { utils::slog.e << "ERROR: Unable to parse compute shader:" << utils::io::endl; - utils::slog.e << tShader.getInfoLog() << utils::io::flush; + utils::slog.e << tShader.getInfoLog() << utils::io::endl; return false; } @@ -214,7 +214,7 @@ std::optional GLSLTools::analyzeFragmentShader( bool const ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg); if (!ok) { utils::slog.e << "ERROR: Unable to parse fragment shader:" << utils::io::endl; - utils::slog.e << tShader.getInfoLog() << utils::io::flush; + utils::slog.e << tShader.getInfoLog() << utils::io::endl; return std::nullopt; } @@ -294,7 +294,7 @@ bool GLSLTools::analyzeVertexShader(const std::string& shaderCode, bool const ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg); if (!ok) { utils::slog.e << "ERROR: Unable to parse vertex shader" << utils::io::endl; - utils::slog.e << tShader.getInfoLog() << utils::io::flush; + utils::slog.e << tShader.getInfoLog() << utils::io::endl; return false; } diff --git a/libs/filamat/src/sca/builtinResource.h b/libs/filamat/src/sca/builtinResource.h index 53ef69ccc4c..d58f2a9d59a 100644 --- a/libs/filamat/src/sca/builtinResource.h +++ b/libs/filamat/src/sca/builtinResource.h @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "glslang/Include/intermediate.h" + const TBuiltInResource DefaultTBuiltInResource = { /* .MaxLights = */ 32, /* .MaxClipPlanes = */ 6, diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 2653eb1313c..13c6bd07686 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -63,17 +63,21 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << "#extension GL_OES_EGL_image_external : require\n\n"; } } - if (material.has3dSamplers && mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { - out << "#extension GL_OES_texture_3D : require\n\n"; - } - if (v.hasInstancedStereo() && stage == ShaderStage::VERTEX) { - // If we're not processing the shader through glslang (in the case of unoptimized - // OpenGL shaders), then we need to add the #extension string ourselves. - // If we ARE running the shader through glslang, then we must not include it, - // otherwise glslang will complain. - out << "#ifndef FILAMENT_GLSLANG\n"; - out << "#extension GL_EXT_clip_cull_distance : require\n"; - out << "#endif\n\n"; + if (v.hasStereo() && stage == ShaderStage::VERTEX) { + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + // If we're not processing the shader through glslang (in the case of unoptimized + // OpenGL shaders), then we need to add the #extension string ourselves. + // If we ARE running the shader through glslang, then we must not include it, + // otherwise glslang will complain. + out << "#ifndef FILAMENT_GLSLANG\n"; + out << "#extension GL_EXT_clip_cull_distance : require\n"; + out << "#endif\n\n"; + break; + case StereoscopicType::MULTIVIEW: + out << "#extension GL_OVR_multiview2 : require\n"; + break; + } } break; case ShaderModel::DESKTOP: @@ -86,13 +90,38 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << "#version 410 core\n\n"; out << "#extension GL_ARB_shading_language_packing : enable\n\n"; } + if (v.hasStereo() && stage == ShaderStage::VERTEX) { + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + // Nothing to generate + break; + case StereoscopicType::MULTIVIEW: + out << "#extension GL_OVR_multiview2 : require\n"; + break; + } + } break; } + if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { + out << "#extension GL_OES_standard_derivatives : require\n\n"; + } + // This allows our includer system to use the #line directive to denote the source file for // #included code. This way, glslang reports errors more accurately. out << "#extension GL_GOOGLE_cpp_style_line_directive : enable\n\n"; + if (v.hasStereo() && stage == ShaderStage::VERTEX) { + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + // Nothing to generate + break; + case StereoscopicType::MULTIVIEW: + out << "layout(num_views = " << material.stereoscopicEyeCount << ") in;\n"; + break; + } + } + if (stage == ShaderStage::COMPUTE) { out << "layout(local_size_x = " << material.groupSize.x << ", local_size_y = " << material.groupSize.y @@ -150,6 +179,45 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << "#define FILAMENT_HAS_FEATURE_INSTANCING\n"; } + // During compilation and optimization, __VERSION__ reflects the shader language version of the + // intermediate code, not the version of the final code. spirv-cross automatically adapts + // certain language features (e.g. fragment output) but leaves others untouched (e.g. sampler + // functions, bit shift operations). Client code may have to make decisions based on this + // information, so define a FILAMENT_EFFECTIVE_VERSION constant. + const char *effective_version; + if (mTargetLanguage == TargetLanguage::GLSL) { + effective_version = "__VERSION__"; + } else { + switch (mShaderModel) { + case ShaderModel::MOBILE: + if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) { + effective_version = "300"; + } else { + effective_version = "100"; + } + break; + case ShaderModel::DESKTOP: + if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) { + effective_version = "450"; + } else { + effective_version = "410"; + } + break; + default: + assert(false); + } + } + generateDefine(out, "FILAMENT_EFFECTIVE_VERSION", effective_version); + + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + generateDefine(out, "FILAMENT_STEREO_INSTANCED", true); + break; + case StereoscopicType::MULTIVIEW: + generateDefine(out, "FILAMENT_STEREO_MULTIVIEW", true); + break; + } + if (stage == ShaderStage::VERTEX) { CodeGenerator::generateDefine(out, "FLIP_UV_ATTRIBUTE", material.flipUV); CodeGenerator::generateDefine(out, "LEGACY_MORPHING", material.useLegacyMorphing); @@ -163,11 +231,13 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) { if (stage == ShaderStage::VERTEX) { generateDefine(out, "VARYING", "out"); + generateDefine(out, "ATTRIBUTE", "in"); } else if (stage == ShaderStage::FRAGMENT) { generateDefine(out, "VARYING", "in"); } } else { generateDefine(out, "VARYING", "varying"); + generateDefine(out, "ATTRIBUTE", "attribute"); } auto getShadingDefine = [](Shading shading) -> const char* { @@ -238,6 +308,10 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade generateSpecializationConstant(out, "CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP", +ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP, false); + // froxel visualization + generateSpecializationConstant(out, "CONFIG_DEBUG_FROXEL_VISUALIZATION", + +ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION, false); + // Workaround a Metal pipeline compilation error with the message: // "Could not statically determine the target of a texture". See light_indirect.fs generateSpecializationConstant(out, "CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND", @@ -246,10 +320,13 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade generateSpecializationConstant(out, "CONFIG_POWER_VR_SHADER_WORKAROUNDS", +ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, false); - // CONFIG_STEREOSCOPIC_EYES is used to size arrays and on Adreno GPUs + vulkan, this has to + generateSpecializationConstant(out, "CONFIG_STEREO_EYE_COUNT", + +ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, material.stereoscopicEyeCount); + + // CONFIG_MAX_STEREOSCOPIC_EYES is used to size arrays and on Adreno GPUs + vulkan, this has to // be explicitly, statically defined (as in #define). Otherwise (using const int for // example), we'd run into a GPU crash. - out << "#define CONFIG_STEREOSCOPIC_EYES " << (int) CONFIG_STEREOSCOPIC_EYES << "\n"; + out << "#define CONFIG_MAX_STEREOSCOPIC_EYES " << (int) CONFIG_MAX_STEREOSCOPIC_EYES << "\n"; if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { // On ES2 since we don't have post-processing, we need to emulate EGL_GL_COLORSPACE_KHR, @@ -261,8 +338,9 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << '\n'; out << SHADERS_COMMON_DEFINES_GLSL_DATA; - if (mFeatureLevel > FeatureLevel::FEATURE_LEVEL_0 && - material.featureLevel == FeatureLevel::FEATURE_LEVEL_0) { + if (material.featureLevel == FeatureLevel::FEATURE_LEVEL_0 && + (mFeatureLevel > FeatureLevel::FEATURE_LEVEL_0 + || mTargetLanguage == TargetLanguage::SPIRV)) { // Insert compatibility definitions for ESSL 1.0 functions which were removed in ESSL 3.0. // This is the minimum required value according to the OpenGL ES Shading Language Version @@ -417,6 +495,13 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type, return out; } + // Feature level 0 only supports one output. + if (index > 0 && mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { + slog.w << "Discarding an output in the generated ESSL 1.0 shader: index = " << index + << ", name = " << name.c_str() << io::endl; + return out; + } + // TODO: add and support additional variable qualifiers (void) qualifier; assert(qualifier == MaterialBuilder::VariableQualifier::OUT); @@ -430,7 +515,9 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type, // formats behind the scenes. It's an error to output fewer components than the attachment // needs, so we always output a float4 instead of a float3. It's never an error to output extra // components. - if (mTargetApi == TargetApi::METAL) { + // + // Meanwhile, ESSL 1.0 must always write to gl_FragColor, a vec4. + if (mTargetApi == TargetApi::METAL || mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { if (outputType == MaterialBuilder::OutputType::FLOAT3) { outputType = MaterialBuilder::OutputType::FLOAT4; swizzleString = ".rgb"; @@ -441,14 +528,25 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type, const char* materialTypeString = getOutputTypeName(materialOutputType); const char* typeString = getOutputTypeName(outputType); + bool generate_essl3_code = mTargetLanguage == TargetLanguage::SPIRV + || mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1; + out << "\n#define FRAG_OUTPUT" << index << " " << name.c_str(); - out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str(); + if (generate_essl3_code) { + out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str(); + } else { + out << "\n#define FRAG_OUTPUT_AT" << index << " gl_FragColor"; + } out << "\n#define FRAG_OUTPUT_MATERIAL_TYPE" << index << " " << materialTypeString; out << "\n#define FRAG_OUTPUT_PRECISION" << index << " " << precisionString; out << "\n#define FRAG_OUTPUT_TYPE" << index << " " << typeString; out << "\n#define FRAG_OUTPUT_SWIZZLE" << index << " " << swizzleString; - out << "\nlayout(location=" << index << ") out " << precisionString << " " + out << "\n"; + + if (generate_essl3_code) { + out << "\nlayout(location=" << index << ") out " << precisionString << " " << typeString << " output_" << name.c_str() << ";\n"; + } return out; } @@ -550,11 +648,12 @@ io::sstream& CodeGenerator::generateUboAsPlainUniforms(io::sstream& out, ShaderS io::sstream& CodeGenerator::generateBufferInterfaceBlock(io::sstream& out, ShaderStage stage, uint32_t binding, const BufferInterfaceBlock& uib) const { - auto const& infos = uib.getFieldInfoList(); - if (infos.empty()) { + if (uib.isEmptyForFeatureLevel(mFeatureLevel)) { return out; } + auto const& infos = uib.getFieldInfoList(); + if (mTargetLanguage == TargetLanguage::GLSL && mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { // we need to generate a structure instead @@ -716,7 +815,7 @@ io::sstream& CodeGenerator::generateSubpass(io::sstream& out, SubpassInfo subpas } void CodeGenerator::fixupExternalSamplers( - std::string& shader, SamplerInterfaceBlock const& sib) noexcept { + std::string& shader, SamplerInterfaceBlock const& sib, FeatureLevel featureLevel) noexcept { auto const& infos = sib.getSamplerInfoList(); if (infos.empty()) { return; @@ -751,7 +850,10 @@ void CodeGenerator::fixupExternalSamplers( while (shader[index] != '\n') index++; index++; - shader.insert(index, "#extension GL_OES_EGL_image_external_essl3 : require\n"); + const char *extensionLine = (featureLevel >= FeatureLevel::FEATURE_LEVEL_1) + ? "#extension GL_OES_EGL_image_external_essl3 : require\n\n" + : "#extension GL_OES_EGL_image_external : require\n\n"; + shader.insert(index, extensionLine); } } @@ -1013,32 +1115,33 @@ io::sstream& CodeGenerator::generateShaderReflections(utils::io::sstream& out, S char const* CodeGenerator::getConstantName(MaterialBuilder::Property property) noexcept { using Property = MaterialBuilder::Property; switch (property) { - case Property::BASE_COLOR: return "BASE_COLOR"; - case Property::ROUGHNESS: return "ROUGHNESS"; - case Property::METALLIC: return "METALLIC"; - case Property::REFLECTANCE: return "REFLECTANCE"; - case Property::AMBIENT_OCCLUSION: return "AMBIENT_OCCLUSION"; - case Property::CLEAR_COAT: return "CLEAR_COAT"; - case Property::CLEAR_COAT_ROUGHNESS: return "CLEAR_COAT_ROUGHNESS"; - case Property::CLEAR_COAT_NORMAL: return "CLEAR_COAT_NORMAL"; - case Property::ANISOTROPY: return "ANISOTROPY"; - case Property::ANISOTROPY_DIRECTION: return "ANISOTROPY_DIRECTION"; - case Property::THICKNESS: return "THICKNESS"; - case Property::SUBSURFACE_POWER: return "SUBSURFACE_POWER"; - case Property::SUBSURFACE_COLOR: return "SUBSURFACE_COLOR"; - case Property::SHEEN_COLOR: return "SHEEN_COLOR"; - case Property::SHEEN_ROUGHNESS: return "SHEEN_ROUGHNESS"; - case Property::GLOSSINESS: return "GLOSSINESS"; - case Property::SPECULAR_COLOR: return "SPECULAR_COLOR"; - case Property::EMISSIVE: return "EMISSIVE"; - case Property::NORMAL: return "NORMAL"; - case Property::POST_LIGHTING_COLOR: return "POST_LIGHTING_COLOR"; - case Property::CLIP_SPACE_TRANSFORM: return "CLIP_SPACE_TRANSFORM"; - case Property::ABSORPTION: return "ABSORPTION"; - case Property::TRANSMISSION: return "TRANSMISSION"; - case Property::IOR: return "IOR"; - case Property::MICRO_THICKNESS: return "MICRO_THICKNESS"; - case Property::BENT_NORMAL: return "BENT_NORMAL"; + case Property::BASE_COLOR: return "BASE_COLOR"; + case Property::ROUGHNESS: return "ROUGHNESS"; + case Property::METALLIC: return "METALLIC"; + case Property::REFLECTANCE: return "REFLECTANCE"; + case Property::AMBIENT_OCCLUSION: return "AMBIENT_OCCLUSION"; + case Property::CLEAR_COAT: return "CLEAR_COAT"; + case Property::CLEAR_COAT_ROUGHNESS: return "CLEAR_COAT_ROUGHNESS"; + case Property::CLEAR_COAT_NORMAL: return "CLEAR_COAT_NORMAL"; + case Property::ANISOTROPY: return "ANISOTROPY"; + case Property::ANISOTROPY_DIRECTION: return "ANISOTROPY_DIRECTION"; + case Property::THICKNESS: return "THICKNESS"; + case Property::SUBSURFACE_POWER: return "SUBSURFACE_POWER"; + case Property::SUBSURFACE_COLOR: return "SUBSURFACE_COLOR"; + case Property::SHEEN_COLOR: return "SHEEN_COLOR"; + case Property::SHEEN_ROUGHNESS: return "SHEEN_ROUGHNESS"; + case Property::GLOSSINESS: return "GLOSSINESS"; + case Property::SPECULAR_COLOR: return "SPECULAR_COLOR"; + case Property::EMISSIVE: return "EMISSIVE"; + case Property::NORMAL: return "NORMAL"; + case Property::POST_LIGHTING_COLOR: return "POST_LIGHTING_COLOR"; + case Property::POST_LIGHTING_MIX_FACTOR: return "POST_LIGHTING_MIX_FACTOR"; + case Property::CLIP_SPACE_TRANSFORM: return "CLIP_SPACE_TRANSFORM"; + case Property::ABSORPTION: return "ABSORPTION"; + case Property::TRANSMISSION: return "TRANSMISSION"; + case Property::IOR: return "IOR"; + case Property::MICRO_THICKNESS: return "MICRO_THICKNESS"; + case Property::BENT_NORMAL: return "BENT_NORMAL"; } } @@ -1085,13 +1188,12 @@ char const* CodeGenerator::getOutputTypeName(MaterialBuilder::OutputType type) n char const* CodeGenerator::getSamplerTypeName(SamplerType type, SamplerFormat format, bool multisample) const noexcept { - assert(!multisample); // multisample samplers not yet supported. switch (type) { case SamplerType::SAMPLER_2D: switch (format) { - case SamplerFormat::INT: return "isampler2D"; - case SamplerFormat::UINT: return "usampler2D"; - case SamplerFormat::FLOAT: return "sampler2D"; + case SamplerFormat::INT: return multisample ? "isampler2DMS" : "isampler2D"; + case SamplerFormat::UINT: return multisample ? "usampler2DMS" : "usampler2D"; + case SamplerFormat::FLOAT: return multisample ? "sampler2DMS" : "sampler2D"; case SamplerFormat::SHADOW: return "sampler2DShadow"; } case SamplerType::SAMPLER_3D: @@ -1104,9 +1206,9 @@ char const* CodeGenerator::getSamplerTypeName(SamplerType type, SamplerFormat fo } case SamplerType::SAMPLER_2D_ARRAY: switch (format) { - case SamplerFormat::INT: return "isampler2DArray"; - case SamplerFormat::UINT: return "usampler2DArray"; - case SamplerFormat::FLOAT: return "sampler2DArray"; + case SamplerFormat::INT: return multisample ? "isampler2DMSArray": "isampler2DArray"; + case SamplerFormat::UINT: return multisample ? "usampler2DMSArray": "usampler2DArray"; + case SamplerFormat::FLOAT: return multisample ? "sampler2DMSArray": "sampler2DArray"; case SamplerFormat::SHADOW: return "sampler2DArrayShadow"; } case SamplerType::SAMPLER_CUBEMAP: diff --git a/libs/filamat/src/shaders/CodeGenerator.h b/libs/filamat/src/shaders/CodeGenerator.h index 00fbb3ce824..4467ccc7657 100644 --- a/libs/filamat/src/shaders/CodeGenerator.h +++ b/libs/filamat/src/shaders/CodeGenerator.h @@ -161,7 +161,8 @@ class UTILS_PRIVATE CodeGenerator { static utils::io::sstream& generateParameters(utils::io::sstream& out, ShaderStage type); static void fixupExternalSamplers( - std::string& shader, filament::SamplerInterfaceBlock const& sib) noexcept; + std::string& shader, filament::SamplerInterfaceBlock const& sib, + FeatureLevel featureLevel) noexcept; // These constants must match the equivalent in MetalState.h. // These values represent the starting index for uniform, ssbo, and sampler group [[buffer(n)]] diff --git a/libs/filamat/src/shaders/MaterialInfo.h b/libs/filamat/src/shaders/MaterialInfo.h index b12863cede4..4979bed5c3c 100644 --- a/libs/filamat/src/shaders/MaterialInfo.h +++ b/libs/filamat/src/shaders/MaterialInfo.h @@ -54,6 +54,7 @@ struct UTILS_PUBLIC MaterialInfo { bool instanced; bool vertexDomainDeviceJittered; bool userMaterialHasCustomDepth; + int stereoscopicEyeCount; filament::SpecularAmbientOcclusion specularAO; filament::RefractionMode refractionMode; filament::RefractionType refractionType; @@ -68,6 +69,7 @@ struct UTILS_PUBLIC MaterialInfo { filament::SamplerBindingMap samplerBindings; filament::ShaderQuality quality; filament::backend::FeatureLevel featureLevel; + filament::backend::StereoscopicType stereoscopicType; filament::math::uint3 groupSize; using BufferContainer = utils::FixedCapacityVector; diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 674452e997f..92508566857 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -23,6 +23,7 @@ #include +#include "backend/DriverEnums.h" #include "filamat/MaterialBuilder.h" #include "CodeGenerator.h" #include "SibGenerator.h" @@ -50,13 +51,13 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(utils::io::sstream& litVariants && filament::Variant::isShadowReceiverVariant(variant)); CodeGenerator::generateDefine(out, "VARIANT_HAS_VSM", filament::Variant::isVSMVariant(variant)); - CodeGenerator::generateDefine(out, "VARIANT_HAS_INSTANCED_STEREO", - filament::Variant::isStereoVariant(variant)); + CodeGenerator::generateDefine(out, "VARIANT_HAS_STEREO", + hasStereo(variant, featureLevel)); switch (stage) { case ShaderStage::VERTEX: CodeGenerator::generateDefine(out, "VARIANT_HAS_SKINNING_OR_MORPHING", - variant.hasSkinningOrMorphing()); + hasSkinningOrMorphing(variant, featureLevel)); break; case ShaderStage::FRAGMENT: CodeGenerator::generateDefine(out, "VARIANT_HAS_FOG", @@ -156,6 +157,9 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(utils::io::sstream& case BlendingMode::SCREEN: CodeGenerator::generateDefine(out, "BLEND_MODE_SCREEN", true); break; + case BlendingMode::CUSTOM: + CodeGenerator::generateDefine(out, "BLEND_MODE_CUSTOM", true); + break; } switch (material.postLightingBlendingMode) { @@ -174,6 +178,9 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(utils::io::sstream& case BlendingMode::SCREEN: CodeGenerator::generateDefine(out, "POST_LIGHTING_BLEND_MODE_SCREEN", true); break; + case BlendingMode::CUSTOM: + CodeGenerator::generateDefine(out, "POST_LIGHTING_BLEND_MODE_CUSTOM", true); + break; default: break; } @@ -342,12 +349,13 @@ ShaderGenerator::ShaderGenerator( } } -void ShaderGenerator::fixupExternalSamplers(ShaderModel sm, - std::string& shader, MaterialInfo const& material) noexcept { +void ShaderGenerator::fixupExternalSamplers(ShaderModel sm, std::string& shader, + MaterialBuilder::FeatureLevel featureLevel, + MaterialInfo const& material) noexcept { // External samplers are only supported on GL ES at the moment, we must // skip the fixup on desktop targets if (material.hasExternalSamplers && sm == ShaderModel::MOBILE) { - CodeGenerator::fixupExternalSamplers(shader, material.sib); + CodeGenerator::fixupExternalSamplers(shader, material.sib, featureLevel); } } @@ -396,7 +404,7 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, generateSurfaceMaterialVariantProperties(vs, mProperties, mDefines); AttributeBitset attributes = material.requiredAttributes; - if (variant.hasSkinningOrMorphing()) { + if (hasSkinningOrMorphing(variant, featureLevel)) { attributes.set(VertexAttribute::BONE_INDICES); attributes.set(VertexAttribute::BONE_WEIGHTS); if (material.useLegacyMorphing) { @@ -436,7 +444,7 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, UniformBindingPoints::SHADOW, UibGenerator::getShadowUib()); } - if (variant.hasSkinningOrMorphing()) { + if (hasSkinningOrMorphing(variant, featureLevel)) { cg.generateUniforms(vs, ShaderStage::VERTEX, UniformBindingPoints::PER_RENDERABLE_BONES, UibGenerator::getPerRenderableBonesUib()); @@ -747,4 +755,21 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel sm, return fs.c_str(); } +bool ShaderGenerator::hasSkinningOrMorphing( + filament::Variant variant, MaterialBuilder::FeatureLevel featureLevel) noexcept { + return variant.hasSkinningOrMorphing() + // HACK(exv): Ignore skinning/morphing variant when targeting ESSL 1.0. We should + // either properly support skinning on FL0 or build a system in matc which allows + // the set of included variants to differ per-feature level. + && featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0; +} + +bool ShaderGenerator::hasStereo( + filament::Variant variant, MaterialBuilder::FeatureLevel featureLevel) noexcept { + return variant.hasStereo() + // HACK(exv): Ignore stereo variant when targeting ESSL 1.0. We should properly build a + // system in matc which allows the set of included variants to differ per-feature level. + && featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0; +} + } // namespace filament diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index 99152d8f687..59b7f2685ab 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -75,6 +75,7 @@ class ShaderGenerator { * the optimizations have been applied. */ static void fixupExternalSamplers(filament::backend::ShaderModel sm, std::string& shader, + MaterialBuilder::FeatureLevel featureLevel, MaterialInfo const& material) noexcept; private: @@ -110,6 +111,14 @@ class ShaderGenerator { static void appendShader(utils::io::sstream& ss, const utils::CString& shader, size_t lineOffset) noexcept; + static bool hasSkinningOrMorphing( + filament::Variant variant, + MaterialBuilder::FeatureLevel featureLevel) noexcept; + + static bool hasStereo( + filament::Variant variant, + MaterialBuilder::FeatureLevel featureLevel) noexcept; + MaterialBuilder::PropertyList mProperties; MaterialBuilder::VariableList mVariables; MaterialBuilder::OutputList mOutputs; diff --git a/libs/filamat/src/shaders/UibGenerator.cpp b/libs/filamat/src/shaders/UibGenerator.cpp index 31932145b47..37361440274 100644 --- a/libs/filamat/src/shaders/UibGenerator.cpp +++ b/libs/filamat/src/shaders/UibGenerator.cpp @@ -39,7 +39,7 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { { "worldFromViewMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "clipFromViewMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "viewFromClipMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, - { "clipFromWorldMatrix", CONFIG_STEREOSCOPIC_EYES, + { "clipFromWorldMatrix", CONFIG_MAX_STEREOSCOPIC_EYES, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "worldFromClipMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "userWorldFromWorldMatrix",0,Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, @@ -60,6 +60,7 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { { "lodBias", 0, Type::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }, { "refractionLodOffset", 0, Type::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }, + { "derivativesScale", 0, Type::FLOAT2 }, { "oneOverFarMinusNear", 0, Type::FLOAT, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, { "nearOverFarMinusNear", 0, Type::FLOAT, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 }, @@ -71,8 +72,6 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { // AO { "aoSamplingQualityAndEdgeDistance", 0, Type::FLOAT }, { "aoBentNormals", 0, Type::FLOAT }, - { "aoReserved0", 0, Type::FLOAT }, - { "aoReserved1", 0, Type::FLOAT }, // ------------------------------------------------------------------------------------ // Dynamic Lighting [variant: DYN] diff --git a/libs/filamat/tests/test_argBufferFixup.cpp b/libs/filamat/tests/test_argBufferFixup.cpp index 350f7fa2c59..7a214bca34d 100644 --- a/libs/filamat/tests/test_argBufferFixup.cpp +++ b/libs/filamat/tests/test_argBufferFixup.cpp @@ -72,7 +72,7 @@ TEST(ArgBufferFixup, TextureAndSampler) { auto argBuffer = MetalArgumentBuffer::Builder() .name("myArgumentBuffer") - .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT) + .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false) .sampler(1, "samplerA") .build(); auto argBufferStr = argBuffer->getMsl(); @@ -88,13 +88,33 @@ TEST(ArgBufferFixup, TextureAndSampler) { MetalArgumentBuffer::destroy(&argBuffer); } +TEST(ArgBufferFixup, TextureAndSamplerMS) { + auto argBuffer = + MetalArgumentBuffer::Builder() + .name("myArgumentBuffer") + .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, true) + .sampler(1, "samplerA") + .build(); + auto argBufferStr = argBuffer->getMsl(); + + const std::string expected = + "struct myArgumentBuffer {\n" + "texture2d_ms textureA [[id(0)]];\n" + "sampler samplerA [[id(1)]];\n" + "}"; + + EXPECT_EQ(argBuffer->getMsl(), expected); + + MetalArgumentBuffer::destroy(&argBuffer); +} + TEST(ArgBufferFixup, Sorted) { auto argBuffer = MetalArgumentBuffer::Builder() .name("myArgumentBuffer") .sampler(3, "samplerB") - .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT) - .texture(2, "textureB", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT) + .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false) + .texture(2, "textureB", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false) .sampler(1, "samplerA") .build(); auto argBufferStr = argBuffer->getMsl(); @@ -116,12 +136,12 @@ TEST(ArgBufferFixup, TextureTypes) { auto argBuffer = MetalArgumentBuffer::Builder() .name("myArgumentBuffer") - .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::INT) - .texture(1, "textureB", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::UINT) - .texture(2, "textureC", SamplerType::SAMPLER_CUBEMAP, SamplerFormat::FLOAT) - .texture(3, "textureD", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT) - .texture(4, "textureE", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT) - .texture(5, "textureF", SamplerType::SAMPLER_CUBEMAP_ARRAY, SamplerFormat::SHADOW) + .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::INT, false) + .texture(1, "textureB", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::UINT, false) + .texture(2, "textureC", SamplerType::SAMPLER_CUBEMAP, SamplerFormat::FLOAT, false) + .texture(3, "textureD", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, false) + .texture(4, "textureE", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT, false) + .texture(5, "textureF", SamplerType::SAMPLER_CUBEMAP_ARRAY, SamplerFormat::SHADOW, false) .build(); auto argBufferStr = argBuffer->getMsl(); @@ -147,7 +167,7 @@ TEST(ArgBufferFixup, InvalidType) { auto argBuffer = MetalArgumentBuffer::Builder() .name("myArgumentBuffer") - .texture(0, "textureA", SamplerType::SAMPLER_3D, SamplerFormat::SHADOW) + .texture(0, "textureA", SamplerType::SAMPLER_3D, SamplerFormat::SHADOW, false) .build(); MetalArgumentBuffer::destroy(&argBuffer); }, "failed assertion"); diff --git a/libs/filamat/tests/test_filamat.cpp b/libs/filamat/tests/test_filamat.cpp index ec15d62d346..1d63fc15dcd 100644 --- a/libs/filamat/tests/test_filamat.cpp +++ b/libs/filamat/tests/test_filamat.cpp @@ -701,6 +701,7 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerOutputFactor) { void material(inout MaterialInputs material) { prepareMaterial(material); material.postLightingColor = vec4(1.0); + material.postLightingMixFactor = 0.5; } )"); @@ -712,6 +713,7 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerOutputFactor) { glslTools.findProperties(ShaderStage::FRAGMENT, shaderCode, properties); MaterialBuilder::PropertyList expected{ false }; expected[size_t(filamat::MaterialBuilder::Property::POST_LIGHTING_COLOR)] = true; + expected[size_t(filamat::MaterialBuilder::Property::POST_LIGHTING_MIX_FACTOR)] = true; EXPECT_TRUE(PropertyListsMatch(expected, properties)); } @@ -872,24 +874,6 @@ TEST_F(MaterialCompiler, FeatureLevel0Sampler2D) { EXPECT_TRUE(result.isValid()); } -TEST_F(MaterialCompiler, FeatureLevel0Sampler3D) { - std::string shaderCode(R"( - void material(inout MaterialInputs material) { - prepareMaterial(material); - material.baseColor = texture3D(materialParams_sampler, vec3(0.0, 0.0, 0.0)); - } - )"); - filamat::MaterialBuilder builder; - builder.parameter("sampler", SamplerType::SAMPLER_3D); - - builder.featureLevel(FeatureLevel::FEATURE_LEVEL_0); - builder.shading(filament::Shading::UNLIT); - builder.material(shaderCode.c_str()); - builder.printShaders(true); - filamat::Package result = builder.build(*jobSystem); - EXPECT_TRUE(result.isValid()); -} - TEST_F(MaterialCompiler, FeatureLevel0Ess3CallFails) { std::string shaderCode(R"( void material(inout MaterialInputs material) { diff --git a/libs/filamentapp/CMakeLists.txt b/libs/filamentapp/CMakeLists.txt index a2c1d0dedbd..2e0e95b7da4 100644 --- a/libs/filamentapp/CMakeLists.txt +++ b/libs/filamentapp/CMakeLists.txt @@ -88,13 +88,23 @@ file(MAKE_DIRECTORY ${MATERIAL_DIR}) file(MAKE_DIRECTORY ${RESOURCE_DIR}) set(RESOURCE_BINS) + +set (MATC_FLAGS ${MATC_BASE_FLAGS}) +if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=instanced) + add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_INSTANCED) +elseif (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=multiview) + add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_MULTIVIEW) +endif () + foreach (mat_src ${MATERIAL_SRCS}) get_filename_component(localname "${mat_src}" NAME_WE) get_filename_component(fullname "${mat_src}" ABSOLUTE) set(output_path "${MATERIAL_DIR}/${localname}.filamat") add_custom_command( OUTPUT ${output_path} - COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} + COMMAND matc ${MATC_FLAGS} -o ${output_path} ${fullname} MAIN_DEPENDENCY ${mat_src} DEPENDS matc COMMENT "Compiling material ${mat_src} to ${output_path}" diff --git a/libs/filamentapp/include/filamentapp/Config.h b/libs/filamentapp/include/filamentapp/Config.h index 1343339d808..4f55cf866bb 100644 --- a/libs/filamentapp/include/filamentapp/Config.h +++ b/libs/filamentapp/include/filamentapp/Config.h @@ -34,6 +34,7 @@ struct Config { filament::camutils::Mode cameraMode = filament::camutils::Mode::ORBIT; bool resizeable = true; bool headless = false; + int stereoscopicEyeCount = 2; // Provided to indicate GPU preference for vulkan std::string vulkanGPUHint; diff --git a/libs/filamentapp/include/filamentapp/FilamentApp.h b/libs/filamentapp/include/filamentapp/FilamentApp.h index ce1466362ac..186d146d083 100644 --- a/libs/filamentapp/include/filamentapp/FilamentApp.h +++ b/libs/filamentapp/include/filamentapp/FilamentApp.h @@ -85,6 +85,8 @@ class FilamentApp { PostRenderCallback postRender = PostRenderCallback(), size_t width = 1024, size_t height = 640); + void reconfigureCameras() { mReconfigureCameras = true; } + filament::Material const* getDefaultMaterial() const noexcept { return mDefaultMaterial; } filament::Material const* getTransparentMaterial() const noexcept { return mTransparentMaterial; } IBL* getIBL() const noexcept { return mIBL.get(); } @@ -189,6 +191,7 @@ class FilamentApp { void fixupMouseCoordinatesForHdpi(ssize_t& x, ssize_t& y) const; FilamentApp* const mFilamentApp = nullptr; + Config mConfig; const bool mIsHeadless; SDL_Window* mWindow = nullptr; @@ -251,6 +254,7 @@ class FilamentApp { float mCameraFocalLength = 28.0f; float mCameraNear = 0.1f; float mCameraFar = 100.0f; + bool mReconfigureCameras = false; #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) filament::backend::VulkanPlatform* mVulkanPlatform = nullptr; diff --git a/libs/filamentapp/materials/transparentColor.mat b/libs/filamentapp/materials/transparentColor.mat index c43f9410e7d..7ad51944dd4 100644 --- a/libs/filamentapp/materials/transparentColor.mat +++ b/libs/filamentapp/materials/transparentColor.mat @@ -9,7 +9,8 @@ material { blending : transparent, culling : none, depthCulling : false, - shadingModel : unlit + shadingModel : unlit, + featureLevel : 0 } fragment { diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 51ba611d14a..82eba7a2316 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -430,8 +430,10 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, window->mDebugCamera->lookAt(eye, center, up); // Update the cube distortion matrix used for frustum visualization. - const Camera* lightmapCamera = window->mMainView->getView()->getDirectionalLightCamera(); - lightmapCube->mapFrustum(*mEngine, lightmapCamera); + const Camera* lightmapCamera = window->mMainView->getView()->getDirectionalShadowCamera(); + if (lightmapCamera) { + lightmapCube->mapFrustum(*mEngine, lightmapCamera); + } cameraCube->mapFrustum(*mEngine, window->mMainCamera); // Delay rendering for roughly one monitor refresh interval @@ -448,6 +450,11 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, preRender(mEngine, window->mViews[0]->getView(), mScene, renderer); } + if (mReconfigureCameras) { + window->configureCamerasForWindow(); + mReconfigureCameras = false; + } + if (renderer->beginFrame(window->getSwapChain())) { for (filament::View* offscreenView : mOffscreenViews) { renderer->render(offscreenView); @@ -568,7 +575,7 @@ void FilamentApp::initSDL() { FilamentApp::Window::Window(FilamentApp* filamentApp, const Config& config, std::string title, size_t w, size_t h) - : mFilamentApp(filamentApp), mIsHeadless(config.headless) { + : mFilamentApp(filamentApp), mConfig(config), mIsHeadless(config.headless) { const int x = SDL_WINDOWPOS_CENTERED; const int y = SDL_WINDOWPOS_CENTERED; uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI; @@ -596,6 +603,14 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, } #endif + Engine::Config engineConfig = {}; + engineConfig.stereoscopicEyeCount = config.stereoscopicEyeCount; +#if defined(FILAMENT_SAMPLES_STEREO_TYPE_INSTANCED) + engineConfig.stereoscopicType = Engine::StereoscopicType::INSTANCED; +#elif defined (FILAMENT_SAMPLES_STEREO_TYPE_MULTIVIEW) + engineConfig.stereoscopicType = Engine::StereoscopicType::MULTIVIEW; +#endif + if (backend == Engine::Backend::VULKAN) { #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) mFilamentApp->mVulkanPlatform = @@ -603,10 +618,16 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, return Engine::Builder() .backend(backend) .platform(mFilamentApp->mVulkanPlatform) + .featureLevel(config.featureLevel) + .config(&engineConfig) .build(); #endif } - return Engine::Builder().backend(backend).build(); + return Engine::Builder() + .backend(backend) + .featureLevel(config.featureLevel) + .config(&engineConfig) + .build(); }; if (config.headless) { @@ -647,14 +668,13 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, #endif - // Select the feature level to use - config.featureLevel = std::min(config.featureLevel, - mFilamentApp->mEngine->getSupportedFeatureLevel()); - mFilamentApp->mEngine->setActiveFeatureLevel(config.featureLevel); + // Write back the active feature level. + config.featureLevel = mFilamentApp->mEngine->getActiveFeatureLevel(); mSwapChain = mFilamentApp->mEngine->createSwapChain( nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); } + mRenderer = mFilamentApp->mEngine->createRenderer(); // create cameras @@ -701,7 +721,10 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, mGodView->setCameraManipulator(mDebugCameraMan); // Ortho view obviously uses an ortho camera - mOrthoView->setCamera( (Camera *)mMainView->getView()->getDirectionalLightCamera() ); + Camera const* debugDirectionalShadowCamera = mMainView->getView()->getDirectionalShadowCamera(); + if (debugDirectionalShadowCamera) { + mOrthoView->setCamera(const_cast(debugDirectionalShadowCamera)); + } } // configure the cameras @@ -873,12 +896,23 @@ void FilamentApp::Window::configureCamerasForWindow() { double near = mFilamentApp->mCameraNear; double far = mFilamentApp->mCameraFar; - mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far); + if (mMainView->getView()->getStereoscopicOptions().enabled) { + mat4 projections[4]; + projections[0] = Camera::projection(mFilamentApp->mCameraFocalLength, 1.0, near, far); + projections[1] = projections[0]; + // simulate foveated rendering + projections[2] = Camera::projection(mFilamentApp->mCameraFocalLength * 2.0, 1.0, near, far); + projections[3] = projections[2]; + mMainCamera->setCustomEyeProjection(projections, 4, projections[0], near, far); + } else { + mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far); + } mDebugCamera->setProjection(45.0, double(width) / height, 0.0625, 4096, Camera::Fov::VERTICAL); auto aspectRatio = double(mainWidth) / height; if (mMainView->getView()->getStereoscopicOptions().enabled) { - aspectRatio = double(mainWidth) / 2.0 / height; + const int ec = mConfig.stereoscopicEyeCount; + aspectRatio = double(mainWidth) / ec / height; } mMainCamera->setScaling({1.0 / aspectRatio, 1.0}); diff --git a/libs/geometry/CMakeLists.txt b/libs/geometry/CMakeLists.txt index d09e1c5f538..a7e8f398eb9 100644 --- a/libs/geometry/CMakeLists.txt +++ b/libs/geometry/CMakeLists.txt @@ -51,7 +51,10 @@ endif() # ================================================================================================== # Installation # ================================================================================================== -install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR}) + +# No need to install since we're combining this lib and the dependent libs into a combined lib +# install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR}) + install(DIRECTORY ${PUBLIC_HDR_DIR}/geometry DESTINATION include) set(COMBINED_DEPS diff --git a/libs/geometry/include/geometry/TangentSpaceMesh.h b/libs/geometry/include/geometry/TangentSpaceMesh.h index 616245a5ce4..980e81ac28f 100644 --- a/libs/geometry/include/geometry/TangentSpaceMesh.h +++ b/libs/geometry/include/geometry/TangentSpaceMesh.h @@ -21,6 +21,8 @@ #include #include +#include + namespace filament { namespace geometry { @@ -106,16 +108,24 @@ class TangentSpaceMesh { * - http://people.compute.dtu.dk/jerf/code/hairy/ */ FRISVAD = 4, + }; - /** - * Flat Shading - * - * **Requires**: `positions + indices`
    - * **Note**: Will remesh - */ - FLAT_SHADING = 5 + /** + * This enum specifies the auxiliary attributes of each vertex that can be provided as input. + * These attributes do not affect the computation of the tangent space, but they will be + * properly mapped when a remeshing is carried out. + */ + enum class AuxAttribute : uint8_t { + UV1 = 0x0, + COLORS = 0x1, + JOINTS = 0x2, + WEIGHTS = 0x3, }; + using InData = std::variant; + /** * Use this class to provide input to the TangentSpaceMesh computation. **Important**: * Computation of the tangent space is intended to be synchronous (working on the same thread). @@ -170,6 +180,16 @@ class TangentSpaceMesh { */ Builder& uvs(filament::math::float2 const* uvs, size_t stride = 0) noexcept; + /** + * Sets "auxiliary" attributes that will be properly mapped when remeshed. + * + * @param attribute The attribute of the data to be stored + * @param data The data to be store + * @param stride The stride for iterating through `attribute` + * @return Builder + */ + Builder& aux(AuxAttribute attribute, InData data, size_t stride = 0) noexcept; + /** * @param positions The input positions * @param stride The stride for iterating through `positions` @@ -177,10 +197,30 @@ class TangentSpaceMesh { */ Builder& positions(filament::math::float3 const* positions, size_t stride = 0) noexcept; + /** + * @param triangleCount The input number of triangles + * @return Builder + */ Builder& triangleCount(size_t triangleCount) noexcept; + + /** + * @param triangles The triangles in 32-bit indices + * @return Builder + */ Builder& triangles(filament::math::uint3 const* triangles) noexcept; + + /** + * @param triangles The triangles in 16-bit indices + * @return Builder + */ Builder& triangles(filament::math::ushort3 const* triangles) noexcept; + /** + * The Client can provide an algorithm hint to produce the tangents. + * + * @param algorithm The algorithm hint. + * @return Builder + */ Builder& algorithm(Algorithm algorithm) noexcept; /** @@ -199,7 +239,7 @@ class TangentSpaceMesh { }; /** - * Destory the mesh object + * Destroy the mesh object * @param mesh A pointer to a TangentSpaceMesh ready to be destroyed */ static void destroy(TangentSpaceMesh* mesh) noexcept; @@ -284,6 +324,24 @@ class TangentSpaceMesh { */ void getQuats(filament::math::quath* out, size_t stride = 0) const noexcept; + /** + * Get output auxiliary attributes. + * Assumes the `out` param is at least of getVertexCount() length (while accounting for + * `stride`). + * + * @param out Client-allocated array that will be used for copying out attribute as T + * @param stride Stride for iterating through `out` + */ + 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> + void getAux(AuxAttribute attribute, T* out, size_t stride = 0) const noexcept; + /** * Get number of output triangles. * The number of output triangles is the same as the number of input triangles. However, when a @@ -315,9 +373,9 @@ class TangentSpaceMesh { void getTriangles(filament::math::ushort3* out) const; /** - * @return The algorithm used to compute the output mesh. + * @return Whether the TBN algorithm remeshed the input. */ - Algorithm getAlgorithm() const noexcept; + bool remeshed() const noexcept; private: ~TangentSpaceMesh() noexcept; diff --git a/libs/geometry/src/MikktspaceImpl.cpp b/libs/geometry/src/MikktspaceImpl.cpp index f40da14f0b7..5c5a1b582b1 100644 --- a/libs/geometry/src/MikktspaceImpl.cpp +++ b/libs/geometry/src/MikktspaceImpl.cpp @@ -18,13 +18,15 @@ #include #include - +#include #include #include #include +#include // memcpy + namespace filament::geometry { using namespace filament::math; @@ -75,27 +77,66 @@ void MikktspaceImpl::setTSpaceBasic(SMikkTSpaceContext const* context, float con float3 const pos = *pointerAdd(wrapper->mPositions, vertInd, wrapper->mPositionStride); float3 const n = normalize(*pointerAdd(wrapper->mNormals, vertInd, wrapper->mNormalStride)); float2 const uv = *pointerAdd(wrapper->mUVs, vertInd, wrapper->mUVStride); - float3 const t{fvTangent[0], fvTangent[1], fvTangent[2]}; + float3 const t { fvTangent[0], fvTangent[1], fvTangent[2] }; float3 const b = fSign * normalize(cross(n, t)); // TODO: packTangentFrame actually changes the orientation of b. quatf const quat = mat3f::packTangentFrame({t, b, n}, sizeof(int32_t)); - wrapper->mOutVertices.push_back({pos, uv, quat}); + auto& output = wrapper->mOutputData; + auto const& EMPTY_ELEMENT = wrapper->EMPTY_ELEMENT; + + size_t const outputCurSize = output.size(); + + // Prepare for the next element + output.insert(output.end(), EMPTY_ELEMENT.begin(), EMPTY_ELEMENT.end()); + + uint8_t* cursor = output.data() + outputCurSize; + + *((float3*) (cursor + POS_OFFSET)) = pos; + *((float2*) (cursor + UV_OFFSET)) = uv; + *((quatf*) (cursor + TBN_OFFSET)) = quat; + + 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; + } } MikktspaceImpl::MikktspaceImpl(const TangentSpaceMeshInput* input) noexcept : mFaceCount((int) input->triangleCount), - mPositions(input->positions), - mPositionStride(input->positionStride ? input->positionStride : sizeof(float3)), - mNormals(input->normals), - mNormalStride(input->normalStride ? input->normalStride : sizeof(float3)), - mUVs(input->uvs), - mUVStride(input->uvStride ? input->uvStride : sizeof(float2)), + mPositions(input->positions()), + mPositionStride(input->positionsStride()), + mNormals(input->normals()), + mNormalStride(input->normalsStride()), + mUVs(input->uvs()), + mUVStride(input->uvsStride()), mIsTriangle16(input->triangles16), mTriangles( - input->triangles16 ? (uint8_t*) input->triangles16 : (uint8_t*) input->triangles32) { - mOutVertices.reserve(mFaceCount * 3); + input->triangles16 ? (uint8_t*) input->triangles16 : (uint8_t*) input->triangles32), + mOutputElementSize(BASE_OUTPUT_SIZE) { + + // We don't know how many attributes there are so we have to create an ordering of the + // output components. The first three components are + // - float3 positions + // - float2 uv + // - quatf tangent + for (auto attrib: input->getAuxAttributes()) { + size_t const attribSize =input->attributeSize(attrib); + mOutputElementSize += attribSize; + mInputAttribArrays.push_back({ + .attrib = attrib, + .data = input->raw(attrib), + .stride = input->stride(attrib), + .size = attribSize, + }); + } + mOutputData.reserve(mFaceCount * 3 * mOutputElementSize); + + // We will insert 0s to signify a new element being added to the output. + EMPTY_ELEMENT = std::vector(mOutputElementSize); } MikktspaceImpl* MikktspaceImpl::getThis(SMikkTSpaceContext const* context) noexcept { @@ -120,25 +161,65 @@ void MikktspaceImpl::run(TangentSpaceMeshOutput* output) noexcept { SMikkTSpaceContext context{.m_pInterface = &interface, .m_pUserData = this}; genTangSpaceDefault(&context); - std::vector remap(mOutVertices.size()); - size_t vertexCount = meshopt_generateVertexRemap(remap.data(), NULL, mOutVertices.size(), - mOutVertices.data(), mOutVertices.size(), sizeof(IOVertex)); + size_t oVertexCount = mOutputData.size() / mOutputElementSize; + std::vector remap; + remap.resize(oVertexCount); + size_t vertexCount = meshopt_generateVertexRemap(remap.data(), NULL, remap.size(), + mOutputData.data(), oVertexCount, mOutputElementSize); - std::vector newVertices(vertexCount); - meshopt_remapVertexBuffer((void*) newVertices.data(), mOutVertices.data(), mOutVertices.size(), - sizeof(IOVertex), remap.data()); + std::vector newVertices(vertexCount * mOutputElementSize); + meshopt_remapVertexBuffer((void*) newVertices.data(), mOutputData.data(), oVertexCount, + mOutputElementSize, remap.data()); uint3* triangles32 = output->triangles32.allocate(mFaceCount); - meshopt_remapIndexBuffer((uint32_t*) triangles32, NULL, mOutVertices.size(), remap.data()); + meshopt_remapIndexBuffer((uint32_t*) triangles32, NULL, remap.size(), remap.data()); + + float3* outPositions = output->positions().allocate(vertexCount); + float2* outUVs = output->uvs().allocate(vertexCount); + quatf* outQuats = output->tspace().allocate(vertexCount); + + 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); + } + } - float3* outPositions = output->positions.allocate(vertexCount); - float2* outUVs = output->uvs.allocate(vertexCount); - quatf* outQuats = output->tangentSpace.allocate(vertexCount); + 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)); - 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* 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 4ed961bdb0e..6add3027faf 100644 --- a/libs/geometry/src/MikktspaceImpl.h +++ b/libs/geometry/src/MikktspaceImpl.h @@ -47,6 +47,16 @@ class MikktspaceImpl { void run(TangentSpaceMeshOutput* output) noexcept; private: + // sizeof(float3 + float2 + quatf) (pos, uv, tangent) + 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; static void getPosition(SMikkTSpaceContext const* context, float fvPosOut[], int const iFace, @@ -72,7 +82,17 @@ class MikktspaceImpl { uint8_t const* mTriangles; bool mIsTriangle16; - std::vector mOutVertices; + struct InputAttribute { + AttributeImpl attrib; + uint8_t const* data; + size_t stride; + size_t size; + }; + std::vector mInputAttribArrays; + + size_t mOutputElementSize; + std::vector mOutputData; + std::vector EMPTY_ELEMENT; }; }// namespace filament::geometry diff --git a/libs/geometry/src/TangentSpaceMesh.cpp b/libs/geometry/src/TangentSpaceMesh.cpp index 91d845ee38e..bb4a74ab105 100644 --- a/libs/geometry/src/TangentSpaceMesh.cpp +++ b/libs/geometry/src/TangentSpaceMesh.cpp @@ -31,21 +31,23 @@ namespace filament { namespace geometry { using namespace filament::math; -using Builder = TangentSpaceMesh::Builder; -using Algorithm = TangentSpaceMesh::Algorithm; -using MethodPtr = void(*)(TangentSpaceMeshInput const*, TangentSpaceMeshOutput*); namespace { -uint8_t const NORMALS_BIT = 0x01; -uint8_t const UVS_BIT = 0x02; -uint8_t const POSITIONS_BIT = 0x04; -uint8_t const INDICES_BIT = 0x08; +using Builder = TangentSpaceMesh::Builder; +using MethodPtr = void(*)(TangentSpaceMeshInput const*, TangentSpaceMeshOutput*); + +constexpr uint8_t const NORMALS_BIT = 0x01; +constexpr uint8_t const UVS_BIT = 0x02; +constexpr uint8_t const POSITIONS_BIT = 0x04; +constexpr uint8_t const TANGENTS_BIT = 0x08; +constexpr uint8_t const INDICES_BIT = 0x10; // Input types -uint8_t const NORMALS = NORMALS_BIT; -uint8_t const POSITIONS_INDICES = POSITIONS_BIT | INDICES_BIT; -uint8_t const NORMALS_UVS_POSITIONS_INDICES = NORMALS_BIT | UVS_BIT | POSITIONS_BIT | INDICES_BIT; +constexpr uint8_t const NORMALS = NORMALS_BIT; +constexpr uint8_t const POSITIONS_INDICES = POSITIONS_BIT | INDICES_BIT; +constexpr uint8_t const NORMALS_UVS_POSITIONS_INDICES = NORMALS_BIT | UVS_BIT | POSITIONS_BIT | INDICES_BIT; +constexpr uint8_t const NORMALS_TANGENTS = NORMALS_BIT | TANGENTS_BIT; std::string_view to_string(Algorithm const algorithm) noexcept { switch (algorithm) { @@ -59,8 +61,25 @@ std::string_view to_string(Algorithm const algorithm) noexcept { return "HUGHES_MOLLER"; case Algorithm::FRISVAD: return "FRISVAD"; - case Algorithm::FLAT_SHADING: + } +} + +std::string_view to_string(AlgorithmImpl const algorithm) noexcept { + switch (algorithm) { + case AlgorithmImpl::INVALID: + return "INVALID"; + case AlgorithmImpl::MIKKTSPACE: + return "MIKKTSPACE"; + case AlgorithmImpl::LENGYEL: + return "LENGYEL"; + case AlgorithmImpl::HUGHES_MOLLER: + return "HUGHES_MOLLER"; + case AlgorithmImpl::FRISVAD: + return "FRISVAD"; + case AlgorithmImpl::FLAT_SHADING: return "FLAT_SHADING"; + case AlgorithmImpl::TANGENTS_PROVIDED: + return "TANGENTS_PROVIDED"; } } @@ -73,75 +92,71 @@ inline void takeStride(InputType*& out, size_t const stride) noexcept { out = pointerAdd(out, 1, stride); } -inline Algorithm selectBestDefaultAlgorithm(uint8_t const inputType) { - Algorithm outAlgo; +inline AlgorithmImpl selectBestDefaultAlgorithm(uint8_t const inputType) { if (isInputType(inputType, NORMALS_UVS_POSITIONS_INDICES)) { - outAlgo = Algorithm::MIKKTSPACE; + return AlgorithmImpl::MIKKTSPACE; + } else if (isInputType(inputType, NORMALS_TANGENTS)) { + return AlgorithmImpl::TANGENTS_PROVIDED; } else if (isInputType(inputType, POSITIONS_INDICES)) { - outAlgo = Algorithm::FLAT_SHADING; + return AlgorithmImpl::FLAT_SHADING; } else { ASSERT_PRECONDITION(inputType & NORMALS, "Must at least have normals or (positions + indices) as input"); - outAlgo = Algorithm::FRISVAD; + return AlgorithmImpl::FRISVAD; } - return outAlgo; } -Algorithm selectAlgorithm(TangentSpaceMeshInput *input) noexcept { +AlgorithmImpl selectAlgorithm(TangentSpaceMeshInput *input) noexcept { uint8_t inputType = 0; - if (input->normals) { + auto normals = input->normals(); + auto positions = input->positions(); + auto uvs = input->uvs(); + auto tangents = input->tangents(); + + if (normals) { inputType |= NORMALS_BIT; } - if (input->positions) { + if (positions) { inputType |= POSITIONS_BIT; } - if (input->uvs) { + if (uvs) { inputType |= UVS_BIT; } if (input->triangles32 || input->triangles16) { inputType |= INDICES_BIT; } + if (tangents) { + inputType |= TANGENTS_BIT; + } - bool foundAlgo = false; - Algorithm outAlgo = Algorithm::DEFAULT; + AlgorithmImpl outAlgo = AlgorithmImpl::INVALID; switch (input->algorithm) { case Algorithm::DEFAULT: outAlgo = selectBestDefaultAlgorithm(inputType); - foundAlgo = true; break; case Algorithm::MIKKTSPACE: if (isInputType(inputType, NORMALS_UVS_POSITIONS_INDICES)) { - outAlgo = Algorithm::MIKKTSPACE; - foundAlgo = true; + outAlgo = AlgorithmImpl::MIKKTSPACE; } break; case Algorithm::LENGYEL: if (isInputType(inputType, NORMALS_UVS_POSITIONS_INDICES)) { - outAlgo = Algorithm::LENGYEL; - foundAlgo = true; + outAlgo = AlgorithmImpl::LENGYEL; } break; case Algorithm::HUGHES_MOLLER: if (isInputType(inputType, NORMALS)) { - outAlgo = Algorithm::HUGHES_MOLLER; - foundAlgo = true; + outAlgo = AlgorithmImpl::HUGHES_MOLLER; } break; case Algorithm::FRISVAD: if (isInputType(inputType, NORMALS)) { - outAlgo = Algorithm::FRISVAD; - foundAlgo = true; - } - break; - case Algorithm::FLAT_SHADING: - if (isInputType(inputType, POSITIONS_INDICES)) { - outAlgo = Algorithm::FLAT_SHADING; - foundAlgo = true; + outAlgo = AlgorithmImpl::FRISVAD; } break; } - if (!foundAlgo) { + if (outAlgo == AlgorithmImpl::INVALID) { outAlgo = selectBestDefaultAlgorithm(inputType); utils::slog.w << "Cannot satisfy algorithm=" << to_string(input->algorithm) << ". Selected algorithm=" << to_string(outAlgo) << " instead" @@ -170,10 +185,10 @@ inline std::pair frisvadKernel(float3 const& n) { void frisvadMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) noexcept { size_t const vertexCount = input->vertexCount; - quatf* quats = output->tangentSpace.allocate(vertexCount); + quatf* quats = output->tspace().allocate(vertexCount); - float3 const* UTILS_RESTRICT normals = input->normals; - size_t nstride = input->normalStride ? input->normalStride : sizeof(float3); + float3 const* UTILS_RESTRICT normals = input->normals(); + size_t const nstride = input->normalsStride(); for (size_t qindex = 0; qindex < vertexCount; ++qindex) { float3 const n = *normals; @@ -181,11 +196,10 @@ void frisvadMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* o quats[qindex] = mat3f::packTangentFrame({t, b, n}, sizeof(int32_t)); normals = pointerAdd(normals, 1, nstride); } - output->vertexCount = input->vertexCount; output->triangleCount = input->triangleCount; - output->uvs.borrow(input->uvs); - output->positions.borrow(input->positions); + output->passthrough(input->attributeData, {AttributeImpl::UV0, AttributeImpl::POSITIONS}); + output->passthrough(input->attributeData, input->getAuxAttributes()); output->triangles32.borrow(input->triangles32); output->triangles16.borrow(input->triangles16); } @@ -193,10 +207,10 @@ void frisvadMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* o void hughesMollerMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) noexcept { size_t const vertexCount = input->vertexCount; - quatf* quats = output->tangentSpace.allocate(vertexCount); + quatf* quats = output->tspace().allocate(vertexCount); - float3 const* UTILS_RESTRICT normals = input->normals; - size_t nstride = input->normalStride ? input->normalStride : sizeof(float3); + float3 const* UTILS_RESTRICT normals = input->normals(); + size_t const nstride = input->normalsStride(); for (size_t qindex = 0; qindex < vertexCount; ++qindex) { float3 const n = *normals; @@ -215,31 +229,82 @@ void hughesMollerMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutp } output->vertexCount = input->vertexCount; output->triangleCount = input->triangleCount; - output->uvs.borrow(input->uvs); - output->positions.borrow(input->positions); + output->passthrough(input->attributeData, {AttributeImpl::UV0, AttributeImpl::POSITIONS}); + output->passthrough(input->attributeData, input->getAuxAttributes()); output->triangles32.borrow(input->triangles32); output->triangles16.borrow(input->triangles16); } void flatShadingMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) noexcept { - float3 const* positions = input->positions; - size_t const pstride = input->positionStride ? input->positionStride : sizeof(float3); + bool const isTriangle16 = input->triangles16 != nullptr; + size_t const triangleCount = input->triangleCount; + size_t const tstride = isTriangle16 ? sizeof(ushort3) : sizeof(uint3); + size_t const outVertexCount = triangleCount * 3; + + using InData = TangentSpaceMesh::InData; + using OutData = std::variant; + + // We make sure to initialize arrays for the auxilliary attributes that will also be mapped in + // the new mesh. + std::vector> outAttributes; + { + auto const initArray = [output, count = outVertexCount](AttributeImpl attrib, + InData indata) -> OutData { + if (std::holds_alternative(indata)) { + if (std::get(indata)) { + return output->data(attrib).allocate(count); + } + return (float2*) nullptr; + } else if (std::holds_alternative(indata)) { + if (std::get(indata)) { + return output->data(attrib).allocate(count); + } + return (float3*) nullptr; + } else if (std::holds_alternative(indata)) { + if (std::get(indata)) { + return output->data(attrib).allocate(count); + } + return (float4*) nullptr; + } else if (std::holds_alternative(indata)) { + if (std::get(indata)) { + return output->data(attrib).allocate(count); + } + return (ushort3*) nullptr; + } else if (std::holds_alternative(indata)) { + if (std::get(indata)) { + return output->data(attrib).allocate(count); + } + return (ushort4*) nullptr; + } + return (float2*) nullptr; + }; + auto attributes = input->getAuxAttributes(); + if (input->uvs()) { + auto uvs = input->uvs(); + attributes.push_back(AttributeImpl::UV0); + } + std::for_each(attributes.begin(), attributes.end(), + [=, &outAttributes](AttributeImpl attrib) { + auto indata = input->data(attrib); + outAttributes.push_back({ + indata, + initArray(attrib, indata), + attrib, + input->stride(attrib), + }); + }); - float2 const* uvs = input->uvs; - size_t const uvstride = input->uvStride ? input->uvStride : sizeof(float2); + } + + float3 const* positions = input->positions(); + size_t const pstride = input->positionsStride(); - bool const isTriangle16 = input->triangles16 != nullptr; uint8_t const* triangles = isTriangle16 ? (uint8_t const*) input->triangles16 : (uint8_t const*) input->triangles32; - size_t const triangleCount = input->triangleCount; - size_t const tstride = isTriangle16 ? sizeof(ushort3) : sizeof(uint3); - - size_t const outVertexCount = triangleCount * 3; - float3* outPositions = output->positions.allocate(outVertexCount); - float2* outUvs = uvs ? output->uvs.allocate(outVertexCount) : nullptr; - quatf* quats = output->tangentSpace.allocate(outVertexCount); + float3* outPositions = output->positions().allocate(outVertexCount); + quatf* quats = output->tspace().allocate(outVertexCount); size_t const outTriangleCount = triangleCount; uint3* outTriangles = output->triangles32.allocate(outTriangleCount); @@ -269,10 +334,39 @@ void flatShadingMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutpu quats[i1] = tspace; quats[i2] = tspace; - if (outUvs) { - outUvs[i0] = *pointerAdd(uvs, tri.x, uvstride); - outUvs[i1] = *pointerAdd(uvs, tri.y, uvstride); - outUvs[i2] = *pointerAdd(uvs, tri.z, uvstride); + // We need to make sure that the aux data is ported to the new mesh + for (auto& [indata, outdata, attrib, stride]: outAttributes) { + if (std::holds_alternative(indata)) { + float2* out = std::get(outdata); + float2 const* in = std::get(indata); + out[i0] = *pointerAdd(in, tri.x, stride); + out[i1] = *pointerAdd(in, tri.y, stride); + out[i2] = *pointerAdd(in, tri.z, stride); + } else if (std::holds_alternative(indata)) { + float3* out = std::get(outdata); + float3 const* in = std::get(indata); + out[i0] = *pointerAdd(in, tri.x, stride); + out[i1] = *pointerAdd(in, tri.y, stride); + out[i2] = *pointerAdd(in, tri.z, stride); + } else if (std::holds_alternative(indata)) { + float4* out = std::get(outdata); + float4 const* in = std::get(indata); + out[i0] = *pointerAdd(in, tri.x, stride); + out[i1] = *pointerAdd(in, tri.y, stride); + out[i2] = *pointerAdd(in, tri.z, stride); + } else if (std::holds_alternative(indata)) { + ushort3* out = std::get(outdata); + ushort3 const* in = std::get(indata); + out[i0] = *pointerAdd(in, tri.x, stride); + out[i1] = *pointerAdd(in, tri.y, stride); + out[i2] = *pointerAdd(in, tri.z, stride); + } else if (std::holds_alternative(indata)) { + ushort4* out = std::get(outdata); + ushort4 const* in = std::get(indata); + out[i0] = *pointerAdd(in, tri.x, stride); + out[i1] = *pointerAdd(in, tri.y, stride); + out[i2] = *pointerAdd(in, tri.z, stride); + } } } @@ -280,6 +374,40 @@ void flatShadingMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutpu output->triangleCount = outTriangleCount; } +void tangentsProvidedMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) + noexcept { + size_t const vertexCount = input->vertexCount; + quatf* quats = output->tspace().allocate(vertexCount); + + float3 const* normal = input->normals(); + size_t const nstride = input->normalsStride(); + + float4 const* tanvec = input->tangents(); + size_t const tstride = input->tangentsStride(); + + for (size_t qindex = 0; qindex < vertexCount; ++qindex) { + float3 const& n = *pointerAdd(normal, qindex, nstride); + float4 const& t4 = *pointerAdd(tanvec, qindex, nstride); + float3 tv = t4.xyz; + float3 b = t4.w > 0 ? cross(tv, n) : cross(n, tv); + + // Some assets do not provide perfectly orthogonal tangents and normals, so we adjust the + // tangent to enforce orthonormality. We would rather honor the exact normal vector than + // the exact tangent vector since the latter is only used for bump mapping and anisotropic + // lighting. + tv = t4.w > 0 ? cross(n, b) : cross(b, n); + + quats[qindex] = mat3f::packTangentFrame({tv, b, n}); + } + + output->vertexCount = vertexCount; + output->triangleCount = input->triangleCount; + output->passthrough(input->attributeData, {AttributeImpl::UV0, AttributeImpl::POSITIONS}); + output->passthrough(input->attributeData, input->getAuxAttributes()); + output->triangles32.borrow(input->triangles32); + output->triangles16.borrow(input->triangles16); +} + void mikktspaceMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) { MikktspaceImpl impl(input); impl.run(output); @@ -298,14 +426,14 @@ inline float3 randomPerp(float3 const& n) { void lengyelMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) { size_t const vertexCount = input->vertexCount; size_t const triangleCount = input->triangleCount; - size_t const positionStride = input->positionStride ? input->positionStride : sizeof(float3); - size_t const normalStride = input->normalStride ? input->normalStride : sizeof(float3); - size_t const uvStride = input->uvStride ? input->uvStride : sizeof(float2); + size_t const positionStride = input->positionsStride(); + size_t const normalStride = input->normalsStride(); + size_t const uvStride = input->uvsStride(); auto const* triangles16 = input->triangles16; auto const* triangles32 = input->triangles32; - auto const* positions = input->positions; - auto const* uvs = input->uvs; - auto const* normals = input->normals; + auto positions = input->positions(); + auto uvs = input->uvs(); + auto normals = input->normals(); std::vector tan1(vertexCount, float3{0.0f}); std::vector tan2(vertexCount, float3{0.0f}); @@ -351,7 +479,7 @@ void lengyelMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* o tan2[tri.z] += tdir; } - quatf* quats = output->tangentSpace.allocate(vertexCount); + quatf* quats = output->tspace().allocate(vertexCount); for (size_t a = 0; a < vertexCount; a++) { float3 const& n = *pointerAdd(normals, a, normalStride); float3 const& t1 = tan1[a]; @@ -369,12 +497,20 @@ void lengyelMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* o output->vertexCount = vertexCount; output->triangleCount = triangleCount; - output->uvs.borrow(uvs); - output->positions.borrow(positions); + output->passthrough(input->attributeData, {AttributeImpl::UV0, AttributeImpl::POSITIONS}); + output->passthrough(input->attributeData, input->getAuxAttributes()); output->triangles32.borrow(triangles32); 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 @@ -399,20 +535,27 @@ Builder& Builder::vertexCount(size_t vertexCount) noexcept { } Builder& Builder::normals(float3 const* normals, size_t stride) noexcept { - mMesh->mInput->normals = normals; - mMesh->mInput->normalStride = stride; + auxImpl(mMesh->mInput->attributeData, AttributeImpl::NORMALS, normals, stride); return *this; } Builder& Builder::uvs(float2 const* uvs, size_t stride) noexcept { - mMesh->mInput->uvs = uvs; - mMesh->mInput->uvStride = stride; + auxImpl(mMesh->mInput->attributeData, AttributeImpl::UV0, uvs, stride); return *this; } Builder& Builder::positions(float3 const* positions, size_t stride) noexcept { - mMesh->mInput->positions = positions; - mMesh->mInput->positionStride = stride; + auxImpl(mMesh->mInput->attributeData, AttributeImpl::POSITIONS, positions, stride); + return *this; +} + +Builder& Builder::tangents(float4 const* tangents, size_t stride) noexcept { + auxImpl(mMesh->mInput->attributeData, AttributeImpl::TANGENTS, tangents, stride); + return *this; +} + +Builder& Builder::aux(AuxAttribute attribute, InData data, size_t stride) noexcept { + auxImpl(mMesh->mInput->attributeData, static_cast(attribute), data, stride); return *this; } @@ -440,25 +583,34 @@ TangentSpaceMesh* Builder::build() { ASSERT_PRECONDITION(!mMesh->mInput->triangles32 || !mMesh->mInput->triangles16, "Cannot provide both uint32 triangles and uint16 triangles"); - // Work in progress. Not for use. + // Validate whether the provided data for an attribute is of the right data type. + for (auto attribute: mMesh->mInput->getAuxAttributes()) { + ASSERT_PRECONDITION( + TangentSpaceMeshInput::isDataTypeCorrect(attribute, mMesh->mInput->data(attribute)), + "Incorrect attribute data type"); + } + mMesh->mOutput->algorithm = selectAlgorithm(mMesh->mInput); MethodPtr method = nullptr; switch (mMesh->mOutput->algorithm) { - case Algorithm::MIKKTSPACE: + case AlgorithmImpl::MIKKTSPACE: method = mikktspaceMethod; break; - case Algorithm::LENGYEL: + case AlgorithmImpl::LENGYEL: method = lengyelMethod; break; - case Algorithm::HUGHES_MOLLER: + case AlgorithmImpl::HUGHES_MOLLER: method = hughesMollerMethod; break; - case Algorithm::FRISVAD: + case AlgorithmImpl::FRISVAD: method = frisvadMethod; break; - case Algorithm::FLAT_SHADING: + case AlgorithmImpl::FLAT_SHADING: method = flatShadingMethod; break; + case AlgorithmImpl::TANGENTS_PROVIDED: + method = tangentsProvidedMethod; + break; default: break; } @@ -501,9 +653,10 @@ size_t TangentSpaceMesh::getVertexCount() const noexcept { } void TangentSpaceMesh::getPositions(float3* positions, size_t stride) const { - ASSERT_PRECONDITION(mInput->positions, "Must provide input positions"); + auto inPositions = mInput->positions(); + ASSERT_PRECONDITION(inPositions, "Must provide input positions"); stride = stride ? stride : sizeof(decltype(*positions)); - auto outPositions = mOutput->positions.get(); + auto const& outPositions = mOutput->positions(); for (size_t i = 0; i < mOutput->vertexCount; ++i) { *positions = outPositions[i]; takeStride(positions, stride); @@ -511,9 +664,10 @@ void TangentSpaceMesh::getPositions(float3* positions, size_t stride) const { } void TangentSpaceMesh::getUVs(float2* uvs, size_t stride) const { - ASSERT_PRECONDITION(mInput->uvs, "Must provide input UVs"); + auto inUVs = mInput->uvs(); + ASSERT_PRECONDITION(inUVs, "Must provide input positions"); stride = stride ? stride : sizeof(decltype(*uvs)); - auto outUvs = mOutput->uvs.get(); + auto const& outUvs = mOutput->uvs(); for (size_t i = 0; i < mOutput->vertexCount; ++i) { *uvs = outUvs[i]; takeStride(uvs, stride); @@ -528,8 +682,8 @@ void TangentSpaceMesh::getTriangles(uint3* out) const { ASSERT_PRECONDITION(mInput->triangles16 || mInput->triangles32, "Must provide input triangles"); bool const is16 = (bool) mOutput->triangles16; - auto triangles16 = mOutput->triangles16.get(); - auto triangles32 = mOutput->triangles32.get(); + auto const& triangles16 = mOutput->triangles16; + auto const& triangles32 = mOutput->triangles32; size_t const stride = sizeof(decltype(*out)); for (size_t i = 0; i < mOutput->triangleCount; ++i) { *out = is16 ? uint3{triangles16[i]} : triangles32[i]; @@ -541,8 +695,8 @@ void TangentSpaceMesh::getTriangles(ushort3* out) const { ASSERT_PRECONDITION(mInput->triangles16 || mInput->triangles32, "Must provide input triangles"); const bool is16 = (bool) mOutput->triangles16; - auto triangles16 = mOutput->triangles16.get(); - auto triangles32 = mOutput->triangles32.get(); + auto const& triangles16 = mOutput->triangles16; + auto const& triangles32 = mOutput->triangles32; const size_t stride = sizeof(decltype(*out)); for (size_t i = 0, c = mOutput->triangleCount; i < c; ++i) { if (is16) { @@ -562,36 +716,70 @@ void TangentSpaceMesh::getTriangles(ushort3* out) const { void TangentSpaceMesh::getQuats(quatf* out, size_t stride) const noexcept { stride = stride ? stride : sizeof(decltype((*out))); - auto tangentSpace = mOutput->tangentSpace.get(); + auto const& tangents = mOutput->tspace(); size_t const vertexCount = mOutput->vertexCount; for (size_t i = 0; i < vertexCount; ++i) { - *out = tangentSpace[i]; + *out = tangents[i]; takeStride(out, stride); } } void TangentSpaceMesh::getQuats(short4* out, size_t stride) const noexcept { stride = stride ? stride : sizeof(decltype((*out))); - auto tangentSpace = mOutput->tangentSpace.get(); + auto const& tangents = mOutput->tspace(); size_t const vertexCount = mOutput->vertexCount; for (size_t i = 0; i < vertexCount; ++i) { - *out = packSnorm16(tangentSpace[i].xyzw); + *out = packSnorm16(tangents[i].xyzw); takeStride(out, stride); } } void TangentSpaceMesh::getQuats(quath* out, size_t stride) const noexcept { stride = stride ? stride : sizeof(decltype((*out))); - auto tangentSpace = mOutput->tangentSpace.get(); + auto const& tangents = mOutput->tspace(); size_t const vertexCount = mOutput->vertexCount; for (size_t i = 0; i < vertexCount; ++i) { - *out = quath(tangentSpace[i].xyzw); + *out = quath(tangents[i].xyzw); + takeStride(out, stride); + } +} + +template void TangentSpaceMesh::getAux(AuxAttribute attribute, float2* out, + size_t stride) const noexcept; + +template void TangentSpaceMesh::getAux(AuxAttribute attribute, float3* out, + size_t stride) const noexcept; + +template void TangentSpaceMesh::getAux(AuxAttribute attribute, float4* out, + size_t stride) const noexcept; + +template void TangentSpaceMesh::getAux(AuxAttribute attribute, ushort3* out, + size_t stride) const noexcept; + +template void TangentSpaceMesh::getAux(AuxAttribute attribute, ushort4* out, + size_t stride) const noexcept; + +template +void TangentSpaceMesh::getAux(AuxAttribute attribute, T* out, size_t stride) const noexcept { + AttributeImpl attrib = static_cast(attribute); + auto inAux = mInput->data(attrib); + ASSERT_PRECONDITION(inAux, "Must provide input auxilliary attribute"); + stride = stride ? stride : sizeof(decltype(*out)); + auto const& outAux = mOutput->data(attrib); + for (size_t i = 0; i < mOutput->vertexCount; ++i) { + *out = outAux[i]; takeStride(out, stride); } } -Algorithm TangentSpaceMesh::getAlgorithm() const noexcept { - return mOutput->algorithm; +bool TangentSpaceMesh::remeshed() const noexcept { + switch(mOutput->algorithm) { + case AlgorithmImpl::MIKKTSPACE: + case AlgorithmImpl::FLAT_SHADING: + return true; + default: + return false; + } } } diff --git a/libs/geometry/src/TangentSpaceMeshInternal.h b/libs/geometry/src/TangentSpaceMeshInternal.h index 019ab53cd68..fb83f8eaa97 100644 --- a/libs/geometry/src/TangentSpaceMeshInternal.h +++ b/libs/geometry/src/TangentSpaceMeshInternal.h @@ -21,22 +21,99 @@ #include #include +#include #include +#include +#include +#include #include namespace filament::geometry { using namespace filament::math; + +namespace { + using Algorithm = TangentSpaceMesh::Algorithm; +using AuxAttribute = TangentSpaceMesh::AuxAttribute; +using InData = TangentSpaceMesh::InData; + +} // namespace + +template +inline const InputType* pointerAdd(InputType const* ptr, size_t index, size_t stride) noexcept { + return (InputType*) (((uint8_t const*) ptr) + (index * stride)); +} + +template +inline InputType* pointerAdd(InputType* ptr, size_t index, size_t stride) noexcept { + return (InputType*) (((uint8_t*) ptr) + (index * stride)); +} + +// Defines the actual implementation used to compute the TBN, where as TangentSpaceMesh::Algorithm +// is a hint that the client can provide. +enum class AlgorithmImpl : uint8_t { + INVALID = 0, + + MIKKTSPACE = 1, + LENGYEL = 2, + HUGHES_MOLLER = 3, + FRISVAD = 4, + + // Generating flat shading will remesh the input + FLAT_SHADING = 5, + TANGENTS_PROVIDED = 6, +}; + +enum class AttributeImpl : uint8_t { + UV1 = 0x0, + COLORS = 0x1, + JOINTS = 0x2, + WEIGHTS = 0x3, + NORMALS = 0x4, + UV0 = 0x5, + TANGENTS = 0x6, + POSITIONS = 0x7, + TANGENT_SPACE = 0x8, +}; + +#define DATA_TYPE_UV1 float2 +#define DATA_TYPE_COLORS float4 +#define DATA_TYPE_JOINTS ushort4 +#define DATA_TYPE_WEIGHTS float4 +#define DATA_TYPE_NORMALS float3 +#define DATA_TYPE_UV0 float2 +#define DATA_TYPE_TANGENTS float4 +#define DATA_TYPE_POSITIONS float3 +#define DATA_TYPE_TANGENT_SPACE quatf + +#define AUX_ATTRIBUTE_MATCH(attrib) static_assert((uint8_t) AuxAttribute::attrib == (uint8_t) AttributeImpl::attrib) + +// These enums are exposed in the public API, we need to make sure they match the internal enums. +AUX_ATTRIBUTE_MATCH(UV1); +AUX_ATTRIBUTE_MATCH(COLORS); +AUX_ATTRIBUTE_MATCH(JOINTS); +AUX_ATTRIBUTE_MATCH(WEIGHTS); + +#undef AUX_ATTRIBUTE_MATCH + +namespace { + +// Maps an attribute to an array and stride. +struct AttributeDataStride { + InData data; + size_t stride = 0; +}; template class InternalArray { public: - void borrow(T const* ptr) { + void borrow(T const* ptr, size_t stride=sizeof(T)) { assert_invariant(mAllocated.empty()); mBorrowed = ptr; + mBorrowedStride = stride; } T* allocate(size_t size) { @@ -50,57 +127,299 @@ class InternalArray { return mBorrowed || !mAllocated.empty(); } - const T* get() { - assert_invariant((bool) this); + T const& operator [](int i) const { if (mBorrowed) { - return mBorrowed; + return *pointerAdd(mBorrowed, i, mBorrowedStride); } - return mAllocated.data(); + return mAllocated[i]; + } + + T& operator [](int i) { + if (mBorrowed) { + return *pointerAdd(mBorrowed, i, mBorrowedStride); + } + return mAllocated[i]; } private: std::vector mAllocated; T const* mBorrowed = nullptr; + size_t mBorrowedStride = sizeof(T); }; +using ArrayType = std::variant, InternalArray, InternalArray, + InternalArray, InternalArray, InternalArray>; + +ArrayType toArray(AttributeImpl attribute) { + switch (attribute) { + case AttributeImpl::UV1: + return InternalArray{}; + case AttributeImpl::COLORS: + return InternalArray{}; + case AttributeImpl::JOINTS: + return InternalArray{}; + case AttributeImpl::WEIGHTS: + return InternalArray{}; + case AttributeImpl::NORMALS: + return InternalArray{}; + case AttributeImpl::UV0: + return InternalArray{}; + case AttributeImpl::TANGENTS: + return InternalArray{}; + case AttributeImpl::POSITIONS: + return InternalArray{}; + case AttributeImpl::TANGENT_SPACE: + return InternalArray{}; + } +} + +} // namespace + struct TangentSpaceMeshInput { + using AttributeMap = std::unordered_map; + size_t vertexCount = 0; - float3 const* normals = nullptr; - float2 const* uvs = nullptr; - float3 const* positions = nullptr; ushort3 const* triangles16 = nullptr; uint3 const* triangles32 = nullptr; - size_t normalStride = 0; - size_t uvStride = 0; - size_t positionStride = 0; size_t triangleCount = 0; + inline float3 const* positions() const { + return data(AttributeImpl::POSITIONS); + } + + inline size_t positionsStride() const { + return strideSafe(AttributeImpl::POSITIONS); + } + + inline float2 const* uvs() const { + return data(AttributeImpl::UV0); + } + + inline size_t uvsStride() const { + return strideSafe(AttributeImpl::UV0); + } + + inline float4 const* tangents() const { + return data(AttributeImpl::TANGENTS); + } + + inline size_t tangentsStride() const { + return strideSafe(AttributeImpl::TANGENTS); + } + + inline float3 const* normals() const { + return data(AttributeImpl::NORMALS); + } + + inline size_t normalsStride() const { + return strideSafe(AttributeImpl::NORMALS); + } + + static inline size_t attributeSize(AttributeImpl attribute) { + switch (attribute) { + case AttributeImpl::UV1: return sizeof(DATA_TYPE_UV1); + case AttributeImpl::COLORS: return sizeof(DATA_TYPE_COLORS); + case AttributeImpl::JOINTS: return sizeof(DATA_TYPE_JOINTS); + case AttributeImpl::WEIGHTS: return sizeof(DATA_TYPE_WEIGHTS); + case AttributeImpl::NORMALS: return sizeof(DATA_TYPE_NORMALS); + case AttributeImpl::UV0: return sizeof(DATA_TYPE_UV0); + case AttributeImpl::TANGENTS: return sizeof(DATA_TYPE_TANGENTS); + case AttributeImpl::POSITIONS: return sizeof(DATA_TYPE_POSITIONS); + case AttributeImpl::TANGENT_SPACE: + PANIC_POSTCONDITION("Invalid attribute found in input"); + } + } + + static inline bool isDataTypeCorrect(AttributeImpl attribute, InData indata) { + switch (attribute) { + case AttributeImpl::UV1: + return std::holds_alternative(indata); + case AttributeImpl::COLORS: + return std::holds_alternative(indata); + case AttributeImpl::JOINTS: + return std::holds_alternative(indata); + case AttributeImpl::WEIGHTS: + return std::holds_alternative(indata); + case AttributeImpl::NORMALS: + return std::holds_alternative(indata); + case AttributeImpl::UV0: + return std::holds_alternative(indata); + case AttributeImpl::TANGENTS: + return std::holds_alternative(indata); + case AttributeImpl::POSITIONS: + return std::holds_alternative(indata); + case AttributeImpl::TANGENT_SPACE: + PANIC_POSTCONDITION("Invalid attribute found in input"); + } + } + + inline size_t stride(AttributeImpl attribute) const { + auto res = attributeData.find(attribute); + assert_invariant(res != attributeData.end()); + return res->second.stride ? res->second.stride : attributeSize(attribute); + } + + template + inline size_t strideSafe(AttributeImpl attribute) const { + auto res = attributeData.find(attribute); + if (res == attributeData.end()) { + return sizeof(DataType); + + } + return res->second.stride ? res->second.stride : sizeof(DataType); + } + + uint8_t const* raw(AttributeImpl attribute) const { + switch (attribute) { + case AttributeImpl::UV1: + return (uint8_t const*) data(attribute); + case AttributeImpl::COLORS: + return (uint8_t const*) data(attribute); + case AttributeImpl::JOINTS: + return (uint8_t const*) data(attribute); + case AttributeImpl::WEIGHTS: + return (uint8_t const*) data(attribute); + case AttributeImpl::NORMALS: + return (uint8_t const*) data(attribute); + case AttributeImpl::UV0: + return (uint8_t const*) data(attribute); + case AttributeImpl::TANGENTS: + return (uint8_t const*) data(attribute); + case AttributeImpl::POSITIONS: + return (uint8_t const*) data(attribute); + case AttributeImpl::TANGENT_SPACE: + PANIC_POSTCONDITION("Invalid attribute found in input"); + } + } + + // Pass back the std::variant instead of the content. + InData data(AttributeImpl attribute) const { + auto res = attributeData.find(attribute); + assert_invariant(res != attributeData.end()); + return res->second.data; + } + + template + inline DataType const* data(AttributeImpl attribute) const { + auto res = attributeData.find(attribute); + if (res == attributeData.end()) { + return nullptr; + } + return std::get(res->second.data); + } + + inline std::vector getAuxAttributes() const { + std::vector ret; + for (auto [attrib, data]: attributeData) { + // TANGENT_SPACE is only used for output + assert_invariant(attrib != AttributeImpl::TANGENT_SPACE); + + if (attrib == AttributeImpl::POSITIONS || attrib == AttributeImpl::TANGENTS || + attrib == AttributeImpl::UV0 || attrib == AttributeImpl::NORMALS) { + continue; + } + ret.push_back(attrib); + } + return ret; + } + + AttributeMap attributeData; + Algorithm algorithm; }; struct TangentSpaceMeshOutput { - Algorithm algorithm; + TangentSpaceMeshOutput() { + for (AttributeImpl attrib: + {AttributeImpl::TANGENT_SPACE, AttributeImpl::UV0, AttributeImpl::POSITIONS}) { + attributeData.emplace(std::make_pair(attrib, toArray(attrib))); + } + } + + InternalArray& tspace() { + return data(AttributeImpl::TANGENT_SPACE); + } + + InternalArray& uvs() { + return data(AttributeImpl::UV0); + } + + InternalArray& positions() { + return data(AttributeImpl::POSITIONS); + } + + template + InternalArray& data(AttributeImpl attrib) { + if (attributeData.find(attrib) == attributeData.end()) { + attributeData.emplace(std::make_pair(attrib, toArray(attrib))); + } + return std::get>(attributeData[attrib]); + } + + void passthrough(TangentSpaceMeshInput::AttributeMap const& inAttributeMap, + std::vector const& attributes) { + auto const borrow = [&inAttributeMap, this](AttributeImpl attrib) { + auto ref = inAttributeMap.find(attrib); + if (ref == inAttributeMap.end()) { + return; + } + InData const& indata = ref->second.data; + size_t const stride = ref->second.stride; + switch (attrib) { + case AttributeImpl::UV1: + data(attrib).borrow(std::get(indata), + stride); + break; + case AttributeImpl::COLORS: + data(attrib).borrow(std::get(indata), + stride); + break; + case AttributeImpl::JOINTS: + data(attrib).borrow(std::get(indata), + stride); + break; + case AttributeImpl::WEIGHTS: + data(attrib).borrow( + std::get(indata), stride); + break; + case AttributeImpl::NORMALS: + data(attrib).borrow( + std::get(indata), stride); + break; + case AttributeImpl::UV0: + data(attrib).borrow(std::get(indata), + stride); + break; + case AttributeImpl::TANGENTS: + data(attrib).borrow( + std::get(indata), stride); + break; + case AttributeImpl::POSITIONS: + data(attrib).borrow( + std::get(indata), stride); + break; + case AttributeImpl::TANGENT_SPACE: + PANIC_POSTCONDITION("Invalid attribute found in input"); + break; + } + }; + + for (AttributeImpl attrib: attributes) { + borrow(attrib); + } + } + + AlgorithmImpl algorithm; size_t triangleCount = 0; size_t vertexCount = 0; - InternalArray tangentSpace; - InternalArray uvs; - InternalArray positions; InternalArray triangles32; InternalArray triangles16; -}; -template -inline const InputType* pointerAdd(InputType const* ptr, size_t index, size_t stride) noexcept { - return (InputType*) (((uint8_t const*) ptr) + (index * stride)); -} - -template -inline InputType* pointerAdd(InputType* ptr, size_t index, size_t stride) noexcept { - return (InputType*) (((uint8_t*) ptr) + (index * stride)); -} + std::unordered_map attributeData; +}; }// namespace filament::geometry diff --git a/libs/geometry/tests/test_tangent_space_mesh.cpp b/libs/geometry/tests/test_tangent_space_mesh.cpp index 394b7079a68..c9c1e6e3c34 100644 --- a/libs/geometry/tests/test_tangent_space_mesh.cpp +++ b/libs/geometry/tests/test_tangent_space_mesh.cpp @@ -21,13 +21,19 @@ #include +#include + +#include + class TangentSpaceMeshTest : public testing::Test {}; using namespace filament::geometry; using namespace filament::math; namespace { -const std::vector CUBE_VERTS { +using AuxAttribute = TangentSpaceMesh::AuxAttribute; + +std::vector const CUBE_VERTS { float3{0, 0, 0}, float3{0, 0, 1}, float3{0, 1, 0}, @@ -38,7 +44,7 @@ const std::vector CUBE_VERTS { float3{1, 1, 1} }; -const std::vector CUBE_UVS { +std::vector const CUBE_UVS { float2{0, 0}, float2{0, 0}, float2{1, 0}, @@ -49,8 +55,20 @@ const std::vector CUBE_UVS { float2{0, 1} }; -const float3 CUBE_CENTER{.5, .5, .5}; -const std::vector CUBE_NORMALS { +// This is used to verify that attributes are properly mapped for remeshed methods. +std::vector const CUBE_COLORS { + float4{0, 0, 0, 1}, + float4{0, 0, 1, 1}, + float4{0, 1, 0, 1}, + float4{0, 1, 1, 1}, + float4{1, 0, 0, 1}, + float4{1, 0, 1, 1}, + float4{1, 1, 0, 1}, + float4{1, 1, 1, 1}, +}; + +float3 const CUBE_CENTER { .5, .5, .5 }; +std::vector const CUBE_NORMALS { normalize(CUBE_VERTS[0] - CUBE_CENTER), normalize(CUBE_VERTS[1] - CUBE_CENTER), normalize(CUBE_VERTS[2] - CUBE_CENTER), @@ -61,7 +79,19 @@ const std::vector CUBE_NORMALS { normalize(CUBE_VERTS[7] - CUBE_CENTER), }; -const std::vector CUBE_TRIANGLES { +float3 const UP_VEC{1, 0, 0}; +std::vector const CUBE_TANGENTS { + float4{normalize(cross(CUBE_NORMALS[0], UP_VEC)), -1.0}, + float4{normalize(cross(CUBE_NORMALS[1], UP_VEC)), -1.0}, + float4{normalize(cross(CUBE_NORMALS[2], UP_VEC)), -1.0}, + float4{normalize(cross(CUBE_NORMALS[3], UP_VEC)), -1.0}, + float4{normalize(cross(CUBE_NORMALS[4], UP_VEC)), -1.0}, + float4{normalize(cross(CUBE_NORMALS[5], UP_VEC)), -1.0}, + float4{normalize(cross(CUBE_NORMALS[6], UP_VEC)), -1.0}, + float4{normalize(cross(CUBE_NORMALS[7], UP_VEC)), -1.0}, +}; + +std::vector const CUBE_TRIANGLES { ushort3{0, 6, 4}, ushort3{0, 2, 6}, // XY-plane at z=0, normal=(0, 0, -1) ushort3{4, 7, 5}, ushort3{4, 6, 7}, // YZ-plane at x=1, normal=(1, 0 , 0) ushort3{2, 7, 6}, ushort3{2, 3, 7}, // XZ-plane at y=1, normal=(0, 1, 0) @@ -71,7 +101,7 @@ const std::vector CUBE_TRIANGLES { }; // Corresponding to the faces in CUBE_TRIANGLES -const std::vector CUBE_FACE_NORMALS { +std::vector const CUBE_FACE_NORMALS { float3{0, 0, -1}, float3{1, 0, 0}, float3{0, 1, 0}, @@ -80,7 +110,7 @@ const std::vector CUBE_FACE_NORMALS { float3{0, 0, 1} }; -const std::vector TEST_NORMALS { +std::vector const TEST_NORMALS { float3{1, 0, 0}, float3{0, 1, 0}, float3{0, 0, 1}, @@ -89,50 +119,46 @@ const std::vector TEST_NORMALS { normalize(float3{1, 1, 1}) }; -const float3 NORMAL_AXIS{0, 0, 1}; -const float3 TANGENT_AXIS{1, 0, 0}; -const float3 BITANGENT_AXIS{0, 1, 0}; +float3 const NORMAL_AXIS{0, 0, 1}; +float3 const TANGENT_AXIS{1, 0, 0}; +float3 const BITANGENT_AXIS{0, 1, 0}; -bool isAlmostEqual3(const float3& a, const float3& b) noexcept { - const float3 diff = a - b; - const size_t steps = sizeof(float3) / sizeof(float); - for (int i = 0; i < steps; ++i) { - if (abs(diff[i]) > std::numeric_limits::epsilon()) { - return false; - } - } - return true; -} +#define ALMOST_EQUAL() \ + decltype(a) diff = a - b; \ + const size_t steps = sizeof(decltype(a)) / sizeof(float); \ + for (int i = 0; i < steps; ++i) { \ + if (abs(diff[i]) > std::numeric_limits::epsilon()) { return false; } \ + } \ + return true + +bool isAlmostEqual4(const float4& a, const float4& b) noexcept { ALMOST_EQUAL(); } +bool isAlmostEqual3(const float3& a, const float3& b) noexcept { ALMOST_EQUAL(); } +bool isAlmostEqual2(const float2& a, const float2& b) noexcept { ALMOST_EQUAL(); } + +#undef ALMOST_EQUAL -bool isAlmostEqual2(const float2& a, const float2& b) noexcept { - const float2 diff = a - b; - const size_t steps = sizeof(float2) / sizeof(float); - for (int i = 0; i < steps; ++i) { - if (abs(diff[i]) > std::numeric_limits::epsilon()) { - return false; - } - } - return true; -} } // anonymous namespace -TEST_F(TangentSpaceMeshTest, BuilderDefaultAlgorithms) { +TEST_F(TangentSpaceMeshTest, BuilderDefaultAlgorithmsRemeshes) { + // Expect flat shading selected. TangentSpaceMesh* mesh = TangentSpaceMesh::Builder() .vertexCount(CUBE_VERTS.size()) .positions(CUBE_VERTS.data()) .triangleCount(CUBE_TRIANGLES.size()) .triangles(CUBE_TRIANGLES.data()) .build(); - EXPECT_EQ(mesh->getAlgorithm(), TangentSpaceMesh::Algorithm::FLAT_SHADING); + EXPECT_TRUE(mesh->remeshed()); TangentSpaceMesh::destroy(mesh); + // Expect frisvad selected. mesh = TangentSpaceMesh::Builder() .vertexCount(1) .normals(TEST_NORMALS.data()) .build(); - EXPECT_EQ(mesh->getAlgorithm(), TangentSpaceMesh::Algorithm::FRISVAD); + EXPECT_FALSE(mesh->remeshed()); TangentSpaceMesh::destroy(mesh); + // Expect mikktspace selected. mesh = TangentSpaceMesh::Builder() .vertexCount(CUBE_VERTS.size()) .positions(CUBE_VERTS.data()) @@ -141,7 +167,7 @@ TEST_F(TangentSpaceMeshTest, BuilderDefaultAlgorithms) { .triangleCount(CUBE_TRIANGLES.size()) .triangles(CUBE_TRIANGLES.data()) .build(); - EXPECT_EQ(mesh->getAlgorithm(), TangentSpaceMesh::Algorithm::MIKKTSPACE); + EXPECT_TRUE(mesh->remeshed()); TangentSpaceMesh::destroy(mesh); } @@ -153,7 +179,7 @@ TEST_F(TangentSpaceMeshTest, FlatShadingRemesh) { .triangleCount(CUBE_TRIANGLES.size()) .triangles(CUBE_TRIANGLES.data()) .uvs(CUBE_UVS.data()) - .algorithm(TangentSpaceMesh::Algorithm::FLAT_SHADING) + .aux(AuxAttribute::COLORS, CUBE_COLORS.data()) .build(); // Number of triangles should remain the same @@ -165,17 +191,23 @@ TEST_F(TangentSpaceMeshTest, FlatShadingRemesh) { std::vector outUVs(mesh->getVertexCount()); mesh->getUVs(outUVs.data()); + std::vector outColors(mesh->getVertexCount()); + mesh->getAux(AuxAttribute::COLORS, outColors.data()); + for (size_t i = 0; i < outPositions.size(); ++i) { const auto& outPos = outPositions[i]; const auto& outUV = outUVs[i]; + const auto& outColor = outColors[i]; bool found = false; for (size_t j = 0; j < CUBE_VERTS.size(); ++j) { const auto& inPos = CUBE_VERTS[j]; const auto& inUV = CUBE_UVS[j]; + const auto& inColor = CUBE_COLORS[j]; if (isAlmostEqual3(outPos, inPos)) { found = true; EXPECT_PRED2(isAlmostEqual2, outUV, inUV); + EXPECT_PRED2(isAlmostEqual4, outColor, inColor); break; } } @@ -190,7 +222,6 @@ TEST_F(TangentSpaceMeshTest, FlatShading) { .positions(CUBE_VERTS.data()) .triangleCount(CUBE_TRIANGLES.size()) .triangles(CUBE_TRIANGLES.data()) - .algorithm(TangentSpaceMesh::Algorithm::FLAT_SHADING) .build(); ASSERT_EQ(mesh->getVertexCount(), CUBE_TRIANGLES.size() * 3); @@ -211,6 +242,28 @@ TEST_F(TangentSpaceMeshTest, FlatShading) { TangentSpaceMesh::destroy(mesh); } +TEST_F(TangentSpaceMeshTest, TangentsProvided) { + TangentSpaceMesh* mesh = TangentSpaceMesh::Builder() + .vertexCount(CUBE_VERTS.size()) + .normals(CUBE_NORMALS.data()) + .tangents(CUBE_TANGENTS.data()) + .triangleCount(CUBE_TRIANGLES.size()) + .triangles(CUBE_TRIANGLES.data()) + .build(); + + ASSERT_EQ(mesh->getVertexCount(), CUBE_VERTS.size()); + ASSERT_EQ(mesh->getTriangleCount(), CUBE_TRIANGLES.size()); + + size_t const vertexCount = mesh->getVertexCount(); + std::vector quats(vertexCount); + mesh->getQuats(quats.data()); + for (size_t i = 0; i < vertexCount; ++i) { + float3 const n = quats[i] * NORMAL_AXIS; + EXPECT_PRED2(isAlmostEqual3, n, CUBE_NORMALS[i]); + } + TangentSpaceMesh::destroy(mesh); +} + TEST_F(TangentSpaceMeshTest, Frisvad) { TangentSpaceMesh* mesh = TangentSpaceMesh::Builder() .vertexCount(TEST_NORMALS.size()) @@ -273,6 +326,7 @@ TEST_F(TangentSpaceMeshTest, MikktspaceRemesh) { .uvs(CUBE_UVS.data()) .triangleCount(CUBE_TRIANGLES.size()) .triangles(CUBE_TRIANGLES.data()) + .aux(AuxAttribute::COLORS, CUBE_COLORS.data()) .algorithm(TangentSpaceMesh::Algorithm::MIKKTSPACE) .build(); @@ -284,17 +338,23 @@ TEST_F(TangentSpaceMeshTest, MikktspaceRemesh) { std::vector outUVs(vertexCount); mesh->getUVs(outUVs.data()); + std::vector outColors(mesh->getVertexCount()); + mesh->getAux(AuxAttribute::COLORS, outColors.data()); + for (size_t i = 0; i < outPositions.size(); ++i) { auto const& outPos = outPositions[i]; auto const& outUV = outUVs[i]; + auto const& outColor = outColors[i]; bool found = false; for (size_t j = 0; j < CUBE_VERTS.size(); ++j) { auto const& inPos = CUBE_VERTS[j]; auto const& inUV = CUBE_UVS[j]; + auto const& inColor = CUBE_COLORS[j]; if (isAlmostEqual3(outPos, inPos)) { found = true; EXPECT_PRED2(isAlmostEqual2, outUV, inUV); + EXPECT_PRED2(isAlmostEqual4, outColor, inColor); break; } } diff --git a/libs/gltfio/CMakeLists.txt b/libs/gltfio/CMakeLists.txt index d570f61e8e7..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}") @@ -176,6 +190,11 @@ if (WEBGL_PTHREADS) target_compile_definitions(gltfio_core PUBLIC -DFILAMENT_WASM_THREADS) endif() +set(GLTFIO_WARNINGS -Wall -Werror) +if (NOT MSVC) + target_compile_options(gltfio_core PRIVATE ${GLTFIO_WARNINGS}) +endif() + if (NOT WEBGL AND NOT ANDROID AND NOT IOS) # ================================================================================================== @@ -185,10 +204,6 @@ if (NOT WEBGL AND NOT ANDROID AND NOT IOS) target_link_libraries(${TARGET} PUBLIC filamat gltfio_core) target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) set_target_properties(${TARGET} PROPERTIES FOLDER Libs) - - # ================================================================================================== - # Compiler flags - # ================================================================================================== if (NOT MSVC) target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register) endif() @@ -229,7 +244,7 @@ if (TNT_DEV AND NOT WEBGL AND NOT ANDROID AND NOT IOS) target_link_libraries(${TEST_TARGET} PRIVATE ${TARGET} gtest uberarchive) if (NOT MSVC) - target_compile_options(${TEST_TARGET} PRIVATE -Wno-deprecated-register) + target_compile_options(${TEST_TARGET} PRIVATE ${GLTFIO_WARNINGS}) endif() set_target_properties(${TEST_TARGET} PROPERTIES FOLDER Tests) endif() diff --git a/libs/gltfio/include/gltfio/AssetLoader.h b/libs/gltfio/include/gltfio/AssetLoader.h index c031850f3b6..f516166a800 100644 --- a/libs/gltfio/include/gltfio/AssetLoader.h +++ b/libs/gltfio/include/gltfio/AssetLoader.h @@ -97,8 +97,8 @@ struct AssetConfiguration { * * // Load buffers and textures from disk. * ResourceLoader resourceLoader({engine, ".", true}); - * resourceLoader.addTextureProvider("image/png", decoder) - * resourceLoader.addTextureProvider("image/jpeg", decoder) + * resourceLoader.addTextureProvider("image/png", decoder); + * resourceLoader.addTextureProvider("image/jpeg", decoder); * resourceLoader.loadResources(asset); * * // Free the glTF hierarchy as it is no longer needed. @@ -197,7 +197,7 @@ class UTILS_PUBLIC AssetLoader { * This cannot be called after FilamentAsset::releaseSourceData(). * See also AssetLoader::createInstancedAsset(). */ - FilamentInstance* createInstance(FilamentAsset* primary); + FilamentInstance* createInstance(FilamentAsset* asset); /** * Allows clients to enable diagnostic shading on newly-loaded assets. diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h index ab0a6f10fa2..d222871bd7e 100644 --- a/libs/gltfio/include/gltfio/ResourceLoader.h +++ b/libs/gltfio/include/gltfio/ResourceLoader.h @@ -69,9 +69,12 @@ class UTILS_PUBLIC ResourceLoader { public: using BufferDescriptor = filament::backend::BufferDescriptor; - ResourceLoader(const ResourceConfiguration& config); + explicit ResourceLoader(const ResourceConfiguration& config); ~ResourceLoader(); + + void setConfiguration(const ResourceConfiguration& config); + /** * Feeds the binary content of an external resource into the loader's URI cache. * @@ -90,7 +93,8 @@ class UTILS_PUBLIC ResourceLoader { /** * Register a plugin that can consume PNG / JPEG content and produce filament::Texture objects. * - * Destruction of the given provider is the client's responsibility. + * Destruction of the given provider is the client's responsibility and must be done after the + * destruction of this ResourceLoader. */ void addTextureProvider(const char* mimeType, TextureProvider* provider); @@ -153,8 +157,6 @@ class UTILS_PUBLIC ResourceLoader { private: bool loadResources(FFilamentAsset* asset, bool async); - void normalizeSkinningWeights(FFilamentAsset* asset) const; - AssetPool* mPool; 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/ArchiveCache.cpp b/libs/gltfio/src/ArchiveCache.cpp index 47798741fa3..18280b01b79 100644 --- a/libs/gltfio/src/ArchiveCache.cpp +++ b/libs/gltfio/src/ArchiveCache.cpp @@ -16,17 +16,31 @@ #include "ArchiveCache.h" +#include + +#include #include + +#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include +#include + using namespace utils; using namespace filament::uberz; namespace filament::gltfio { - // Set this to a certain spec index to find out why it was deemed unsuitable. // To find the spec index of interest, try invoking uberz with the verbose flag. constexpr static int DEBUG_SPEC_INDEX = -1; @@ -60,6 +74,9 @@ void ArchiveCache::load(const void* archiveData, uint64_t archiveByteCount) { // This loops though all ubershaders and returns the first one that meets the given requirements. Material* ArchiveCache::getMaterial(const ArchiveRequirements& reqs) { assert_invariant(mArchive && "Please call load() before requesting any materials."); + if (mArchive == nullptr) { + return nullptr; + } for (uint64_t i = 0; i < mArchive->specsCount; ++i) { const ArchiveSpec& spec = mArchive->specs[i]; @@ -101,7 +118,7 @@ Material* ArchiveCache::getMaterial(const ArchiveRequirements& reqs) { // mesh doesn't have it, then this ubershader is not suitable. This occurs very rarely, so // it intentionally comes after the other suitability check. for (uint64_t j = 0; j < spec.flagsCount && specIsSuitable; ++j) { - ArchiveFlag& flag = spec.flags[j]; + ArchiveFlag const& flag = spec.flags[j]; if (UTILS_UNLIKELY(flag.value == ArchiveFeature::REQUIRED)) { // This allocates a new CString just to make a robin_map lookup, but this is rare // because almost none of our feature flags are REQUIRED. @@ -118,21 +135,6 @@ Material* ArchiveCache::getMaterial(const ArchiveRequirements& reqs) { mMaterials[i] = Material::Builder() .package(spec.package, spec.packageByteCount) .build(mEngine); - - // Don't attempt to precompile shaders on WebGL. - // Chrome already suffers from slow shader compilation: - // https://github.com/google/filament/issues/6615 - // Precompiling shaders exacerbates the problem. - #if !defined(__EMSCRIPTEN__) - // First compile high priority variants - mMaterials[i]->compile(Material::CompilerPriorityQueue::HIGH, - UserVariantFilterBit::DIRECTIONAL_LIGHTING | - UserVariantFilterBit::DYNAMIC_LIGHTING | - UserVariantFilterBit::SHADOW_RECEIVER); - - // and then, everything else at low priority - mMaterials[i]->compile(Material::CompilerPriorityQueue::LOW); - #endif } return mMaterials[i]; @@ -144,6 +146,7 @@ Material* ArchiveCache::getMaterial(const ArchiveRequirements& reqs) { Material* ArchiveCache::getDefaultMaterial() { assert_invariant(mArchive && "Please call load() before requesting any materials."); assert_invariant(!mMaterials.empty() && "Archive must have at least one material."); + if (!mArchive) return nullptr; if (mMaterials[0] == nullptr) { mMaterials[0] = Material::Builder() .package(mArchive->specs[0].package, mArchive->specs[0].packageByteCount) @@ -173,7 +176,7 @@ FeatureMap ArchiveCache::getFeatureMap(Material* material) const { } ArchiveCache::~ArchiveCache() { - assert_invariant(mMaterials.size() == 0 && + assert_invariant(mMaterials.empty() && "Please call destroyMaterials explicitly to ensure correct destruction order"); utils::aligned_free(mArchive); } diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 3fd23a40a1d..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 @@ -42,6 +43,7 @@ #include #include +#include #include #include #include @@ -51,7 +53,6 @@ #include -#define CGLTF_IMPLEMENTATION #include #include "downcast.h" @@ -84,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; @@ -228,9 +214,7 @@ struct FAssetLoader : public AssetLoader { FFilamentAsset* createAsset(const uint8_t* bytes, uint32_t nbytes); FFilamentAsset* createInstancedAsset(const uint8_t* bytes, uint32_t numBytes, FilamentInstance** instances, size_t numInstances); - FilamentInstance* createInstance(FFilamentAsset* primary); - void importSkins(FFilamentAsset* primary, FFilamentInstance* instance, - const cgltf_data* srcAsset); + FilamentInstance* createInstance(FFilamentAsset* fAsset); static void destroy(FAssetLoader** loader) noexcept { delete *loader; @@ -258,31 +242,33 @@ struct FAssetLoader : public AssetLoader { } private: + void importSkins(FFilamentInstance* instance, const cgltf_data* srcAsset); + // Methods used during the first traveral (creation of VertexBuffer, IndexBuffer, etc) - void createRootAsset(const cgltf_data* srcAsset); - void recursePrimitives(const cgltf_data* srcAsset, const cgltf_node* rootNode); - void createPrimitives(const cgltf_data* srcAsset, const cgltf_node* node, const char* name); - bool createPrimitive(const cgltf_primitive& inPrim, Primitive* outPrim, const char* name); + FFilamentAsset* createRootAsset(const cgltf_data* srcAsset); + void recursePrimitives(const cgltf_node* rootNode, FFilamentAsset* fAsset); + void createPrimitives(const cgltf_node* node, const char* name, FFilamentAsset* fAsset); + bool createPrimitive(const cgltf_primitive& inPrim, const char* name, Primitive* outPrim, + FFilamentAsset* fAsset); // Methods used during subsequent traverals (creation of entities, renderables, etc) - void createInstances(const cgltf_data* srcAsset, size_t numInstances); - FFilamentInstance* createInstance(const cgltf_data* srcAsset); - void recurseEntities(const cgltf_data* srcAsset, const cgltf_node* node, SceneMask scenes, - Entity parent, FFilamentInstance* instance); - void createRenderable(const cgltf_data* srcAsset, const cgltf_node* node, Entity entity, - const char* name, FFilamentInstance* instance); - void createLight(const cgltf_light* light, Entity entity); - void createCamera(const cgltf_camera* camera, Entity entity); + void createInstances(size_t numInstances, FFilamentAsset* fAsset); + void recurseEntities(const cgltf_node* node, SceneMask scenes, Entity parent, + FFilamentAsset* fAsset, FFilamentInstance* instance); + void createRenderable(const cgltf_node* node, Entity entity, const char* name, + FFilamentAsset* fAsset); + void createLight(const cgltf_light* light, Entity entity, FFilamentAsset* fAsset); + void createCamera(const cgltf_camera* camera, Entity entity, FFilamentAsset* fAsset); void addTextureBinding(MaterialInstance* materialInstance, const char* parameterName, const cgltf_texture* srcTexture, bool srgb); - void createMaterialVariants(const cgltf_data* srcAsset, const cgltf_mesh* mesh, Entity entity, + void createMaterialVariants(const cgltf_mesh* mesh, Entity entity, FFilamentAsset* fAsset, FFilamentInstance* instance); // Utility methods that work with MaterialProvider. Material* getMaterial(const cgltf_data* srcAsset, const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor); - MaterialInstance* createMaterialInstance(const cgltf_data* srcAsset, - const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor); + MaterialInstance* createMaterialInstance(const cgltf_material* inputMat, UvMap* uvmap, + bool vertexColor, FFilamentAsset* fAsset); MaterialKey getMaterialKey(const cgltf_data* srcAsset, const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor, cgltf_texture_view* baseColorTexture, @@ -299,8 +285,6 @@ struct FAssetLoader : public AssetLoader { FTrsTransformManager mTrsTransformManager; // Transient state used only for the asset currently being loaded: - FFilamentAsset* mAsset; - tsl::robin_map mRootNodes; const char* mDefaultNodeName; bool mError = false; bool mDiagnosticsEnabled = false; @@ -343,6 +327,7 @@ FFilamentAsset* FAssetLoader::createInstancedAsset(const uint8_t* bytes, uint32_ utils::FixedCapacityVector glbdata(byteCount); std::copy_n(bytes, byteCount, glbdata.data()); + // The ownership of an allocated `sourceAsset` will be moved to FFilamentAsset::mSourceAsset. cgltf_data* sourceAsset; cgltf_result result = cgltf_parse(&options, glbdata.data(), byteCount, &sourceAsset); if (result != cgltf_result_success) { @@ -350,88 +335,122 @@ FFilamentAsset* FAssetLoader::createInstancedAsset(const uint8_t* bytes, uint32_ return nullptr; } - createRootAsset(sourceAsset); + FFilamentAsset* fAsset = createRootAsset(sourceAsset); if (mError) { - delete mAsset; - mAsset = nullptr; + delete fAsset; + fAsset = nullptr; mError = false; return nullptr; } + glbdata.swap(fAsset->mSourceAsset->glbData); - createInstances(sourceAsset, numInstances); + createInstances(numInstances, fAsset); if (mError) { - delete mAsset; - mAsset = nullptr; + delete fAsset; + fAsset = nullptr; mError = false; return nullptr; - } + } - glbdata.swap(mAsset->mSourceAsset->glbData); - std::copy_n(mAsset->mInstances.data(), numInstances, instances); - return mAsset; + std::copy_n(fAsset->mInstances.data(), numInstances, instances); + return fAsset; } -// note there a two overloads; this is the high-level one -FilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary) { - if (!primary->mSourceAsset) { +FilamentInstance* FAssetLoader::createInstance(FFilamentAsset* fAsset) { + if (!fAsset->mSourceAsset) { slog.e << "Source data has been released; asset is frozen." << io::endl; return nullptr; } - const cgltf_data* srcAsset = primary->mSourceAsset->hierarchy; + const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy; if (srcAsset->scenes == nullptr) { slog.e << "There is no scene in the asset." << io::endl; return nullptr; } - FFilamentInstance* instance = createInstance(srcAsset); + auto rootTransform = mTransformManager.getInstance(fAsset->mRoot); + Entity instanceRoot = mEntityManager.create(); + mTransformManager.create(instanceRoot, rootTransform); + + mMaterialInstanceCache = MaterialInstanceCache(srcAsset); + + // Create an instance object, which is a just a lightweight wrapper around a vector of + // entities and an animator. The creation of animator is triggered from ResourceLoader + // because it could require external bin data. + FFilamentInstance* instance = new FFilamentInstance(instanceRoot, fAsset); + + // Check if the asset has variants. + instance->mVariants.reserve(srcAsset->variants_count); + for (cgltf_size i = 0, len = srcAsset->variants_count; i < len; ++i) { + instance->mVariants.push_back({ CString(srcAsset->variants[i].name) }); + } + + // For each scene root, recursively create all entities. + for (const auto& pair : fAsset->mRootNodes) { + recurseEntities(pair.first, pair.second, instanceRoot, fAsset, instance); + } + + importSkins(instance, srcAsset); + + // Now that all entities have been created, the instance can create the animator component. + // Note that it may need to defer actual creation until external buffers are fully loaded. + instance->createAnimator(); + + fAsset->mInstances.push_back(instance); + + // Bounding boxes are not shared because users might call recomputeBoundingBoxes() which can + // be affected by entity transforms. However, upon instance creation we can safely copy over + // the asset's bounding box. + instance->mBoundingBox = fAsset->mBoundingBox; + + mMaterialInstanceCache.flush(&instance->mMaterialInstances); + + fAsset->mDependencyGraph.commitEdges(); - primary->mDependencyGraph.commitEdges(); return instance; } -void FAssetLoader::createRootAsset(const cgltf_data* srcAsset) { +FFilamentAsset* FAssetLoader::createRootAsset(const cgltf_data* srcAsset) { SYSTRACE_CALL(); #if !GLTFIO_DRACO_SUPPORTED for (cgltf_size i = 0; i < srcAsset->extensions_required_count; i++) { if (!strcmp(srcAsset->extensions_required[i], "KHR_draco_mesh_compression")) { slog.e << "KHR_draco_mesh_compression is not supported." << io::endl; - mAsset = nullptr; - return; + return nullptr; } } #endif mDummyBufferObject = nullptr; - mAsset = new FFilamentAsset(&mEngine, mNameManager, &mEntityManager, &mNodeManager, - &mTrsTransformManager, srcAsset); + FFilamentAsset* fAsset = new FFilamentAsset(&mEngine, mNameManager, &mEntityManager, + &mNodeManager, &mTrsTransformManager, srcAsset); // It is not an error for a glTF file to have zero scenes. - mAsset->mScenes.clear(); + fAsset->mScenes.clear(); if (srcAsset->scenes == nullptr) { - return; + return fAsset; } // Create a single root node with an identity transform as a convenience to the client. - mAsset->mRoot = mEntityManager.create(); - mTransformManager.create(mAsset->mRoot); + fAsset->mRoot = mEntityManager.create(); + mTransformManager.create(fAsset->mRoot); // Check if the asset has an extras string. const cgltf_asset& asset = srcAsset->asset; const cgltf_size extras_size = asset.extras.end_offset - asset.extras.start_offset; if (extras_size > 1) { - mAsset->mAssetExtras = CString(srcAsset->json + asset.extras.start_offset, extras_size); + fAsset->mAssetExtras = CString(srcAsset->json + asset.extras.start_offset, extras_size); } // Build a mapping of root nodes to scene membership sets. assert_invariant(srcAsset->scenes_count <= NodeManager::MAX_SCENE_COUNT); - mRootNodes.clear(); + fAsset->mRootNodes.clear(); const size_t sic = std::min(srcAsset->scenes_count, NodeManager::MAX_SCENE_COUNT); - mAsset->mScenes.reserve(sic); + fAsset->mScenes.reserve(sic); for (size_t si = 0; si < sic; ++si) { const cgltf_scene& scene = srcAsset->scenes[si]; - mAsset->mScenes.emplace_back(scene.name); + fAsset->mScenes.emplace_back(scene.name); for (size_t ni = 0, nic = scene.nodes_count; ni < nic; ++ni) { - mRootNodes[scene.nodes[ni]].set(si); + fAsset->mRootNodes[scene.nodes[ni]].set(si); } } @@ -440,13 +459,13 @@ void FAssetLoader::createRootAsset(const cgltf_data* srcAsset) { // transformable entities for "un-scened" nodes in case they have bones. for (size_t i = 0, n = srcAsset->nodes_count; i < n; ++i) { cgltf_node* node = &srcAsset->nodes[i]; - if (node->parent == nullptr && mRootNodes.find(node) == mRootNodes.end()) { - mRootNodes.insert({node, {}}); + if (node->parent == nullptr && fAsset->mRootNodes.find(node) == fAsset->mRootNodes.end()) { + fAsset->mRootNodes.insert({node, {}}); } } - for (const auto& [node, sceneMask] : mRootNodes) { - recursePrimitives(srcAsset, node); + for (const auto& [node, sceneMask] : fAsset->mRootNodes) { + recursePrimitives(node, fAsset); } // Find every unique resource URI and store a pointer to any of the cgltf-owned cstrings @@ -463,32 +482,34 @@ void FAssetLoader::createRootAsset(const cgltf_data* srcAsset) { for (cgltf_size i = 0, len = srcAsset->images_count; i < len; ++i) { addResourceUri(srcAsset->images[i].uri); } - mAsset->mResourceUris.reserve(resourceUris.size()); + fAsset->mResourceUris.reserve(resourceUris.size()); for (std::string_view uri : resourceUris) { - mAsset->mResourceUris.push_back(uri.data()); + fAsset->mResourceUris.push_back(uri.data()); } + + return fAsset; } -void FAssetLoader::recursePrimitives(const cgltf_data* srcAsset, const cgltf_node* node) { +void FAssetLoader::recursePrimitives(const cgltf_node* node, FFilamentAsset* fAsset) { const char* name = getNodeName(node, mDefaultNodeName); name = name ? name : "node"; if (node->mesh) { - createPrimitives(srcAsset, node, name); - mAsset->mRenderableCount++; + createPrimitives(node, name, fAsset); + fAsset->mRenderableCount++; } for (cgltf_size i = 0, len = node->children_count; i < len; ++i) { - recursePrimitives(srcAsset, node->children[i]); + recursePrimitives(node->children[i], fAsset); } } -void FAssetLoader::createInstances(const cgltf_data* srcAsset, size_t numInstances) { +void FAssetLoader::createInstances(size_t numInstances, FFilamentAsset* fAsset) { // Create a separate entity hierarchy for each instance. Note that MeshCache (vertex // buffers and index buffers) and MaterialInstanceCache (materials and textures) help avoid // needless duplication of resources. for (size_t index = 0; index < numInstances; ++index) { - if (createInstance(srcAsset) == nullptr) { + if (createInstance(fAsset) == nullptr) { mError = true; break; } @@ -497,62 +518,21 @@ void FAssetLoader::createInstances(const cgltf_data* srcAsset, size_t numInstanc // Sort the entities so that the renderable ones come first. This allows us to expose // a "renderables only" pointer without storing a separate list. const auto& rm = mEngine.getRenderableManager(); - std::partition(mAsset->mEntities.begin(), mAsset->mEntities.end(), [&rm](Entity a) { + std::partition(fAsset->mEntities.begin(), fAsset->mEntities.end(), [&rm](Entity a) { return rm.hasComponent(a); }); if (mError) { - destroyAsset(mAsset); - mAsset = nullptr; + destroyAsset(fAsset); + fAsset = nullptr; mError = false; } } -// note there a two overloads; this is the low-level one -FFilamentInstance* FAssetLoader::createInstance(const cgltf_data* srcAsset) { - auto rootTransform = mTransformManager.getInstance(mAsset->mRoot); - Entity instanceRoot = mEntityManager.create(); - mTransformManager.create(instanceRoot, rootTransform); - - mMaterialInstanceCache = MaterialInstanceCache(srcAsset); - - // Create an instance object, which is a just a lightweight wrapper around a vector of - // entities and an animator. The creation of animator is triggered from ResourceLoader - // because it could require external bin data. - FFilamentInstance* instance = new FFilamentInstance(instanceRoot, mAsset); - - // Check if the asset has variants. - instance->mVariants.reserve(srcAsset->variants_count); - for (cgltf_size i = 0, len = srcAsset->variants_count; i < len; ++i) { - instance->mVariants.push_back({CString(srcAsset->variants[i].name)}); - } - - // For each scene root, recursively create all entities. - for (const auto& pair : mRootNodes) { - recurseEntities(srcAsset, pair.first, pair.second, instanceRoot, instance); - } - - importSkins(mAsset, instance, srcAsset); - - // Now that all entities have been created, the instance can create the animator component. - // Note that it may need to defer actual creation until external buffers are fully loaded. - instance->createAnimator(); - - mAsset->mInstances.push_back(instance); - - // Bounding boxes are not shared because users might call recomputeBoundingBoxes() which can - // be affected by entity transforms. However, upon instance creation we can safely copy over - // the asset's bounding box. - instance->mBoundingBox = mAsset->mBoundingBox; - - mMaterialInstanceCache.flush(&instance->mMaterialInstances); - - return instance; -} - -void FAssetLoader::recurseEntities(const cgltf_data* srcAsset, const cgltf_node* node, - SceneMask scenes, Entity parent, FFilamentInstance* instance) { +void FAssetLoader::recurseEntities(const cgltf_node* node, SceneMask scenes, Entity parent, + FFilamentAsset* fAsset, FFilamentInstance* instance) { NodeManager& nm = mNodeManager; + const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy; const Entity entity = mEntityManager.create(); nm.create(entity); const auto nodeInstance = nm.getInstance(entity); @@ -577,20 +557,19 @@ void FAssetLoader::recurseEntities(const cgltf_data* srcAsset, const cgltf_node* // Check if this node has an extras string. const cgltf_size extras_size = node->extras.end_offset - node->extras.start_offset; if (extras_size > 0) { - const cgltf_data* srcAsset = mAsset->mSourceAsset->hierarchy; mNodeManager.setExtras(mNodeManager.getInstance(entity), {srcAsset->json + node->extras.start_offset, extras_size}); } // Update the asset's entity list and private node mapping. - mAsset->mEntities.push_back(entity); + fAsset->mEntities.push_back(entity); instance->mEntities.push_back(entity); instance->mNodeMap[node - srcAsset->nodes] = entity; const char* name = getNodeName(node, mDefaultNodeName); if (name) { - mAsset->mNameToEntity[name].push_back(entity); + fAsset->mNameToEntity[name].push_back(entity); if (mNameManager) { mNameManager->addComponent(entity); mNameManager->setName(mNameManager->getInstance(entity), name); @@ -602,34 +581,36 @@ void FAssetLoader::recurseEntities(const cgltf_data* srcAsset, const cgltf_node* // If the node has a mesh, then create a renderable component. if (node->mesh) { - createRenderable(srcAsset, node, entity, name, instance); + createRenderable(node, entity, name, fAsset); if (srcAsset->variants_count > 0) { - createMaterialVariants(srcAsset, node->mesh, entity, instance); + createMaterialVariants(node->mesh, entity, fAsset, instance); } } if (node->light) { - createLight(node->light, entity); + createLight(node->light, entity, fAsset); } if (node->camera) { - createCamera(node->camera, entity); + createCamera(node->camera, entity, fAsset); } for (cgltf_size i = 0, len = node->children_count; i < len; ++i) { - recurseEntities(srcAsset, node->children[i], scenes, entity, instance); + recurseEntities(node->children[i], scenes, entity, fAsset, instance); } } -void FAssetLoader::createPrimitives(const cgltf_data* srcAsset, const cgltf_node* node, - const char* name) { +void FAssetLoader::createPrimitives(const cgltf_node* node, const char* name, + FFilamentAsset* fAsset) { + cgltf_data* gltf = fAsset->mSourceAsset->hierarchy; const cgltf_mesh* mesh = node->mesh; + 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 = mAsset->mMeshCache[mesh - srcAsset->meshes]; + FixedCapacityVector& prims = fAsset->mMeshCache[mesh - gltf->meshes]; if (prims.empty()) { prims.reserve(mesh->primitives_count); prims.resize(mesh->primitives_count); @@ -642,7 +623,7 @@ void FAssetLoader::createPrimitives(const cgltf_data* srcAsset, const cgltf_node const cgltf_primitive& inputPrim = mesh->primitives[index]; // Create a Filament VertexBuffer and IndexBuffer for this prim if we haven't already. - if (!outputPrim.vertices && !createPrimitive(inputPrim, &outputPrim, name)) { + if (!outputPrim.vertices && !createPrimitive(inputPrim, name, &outputPrim, fAsset)) { mError = true; return; } @@ -656,18 +637,19 @@ void FAssetLoader::createPrimitives(const cgltf_data* srcAsset, const cgltf_node cgltf_node_transform_world(node, &worldTransform[0][0]); const Aabb transformed = aabb.transform(worldTransform); - mAsset->mBoundingBox.min = min(mAsset->mBoundingBox.min, transformed.min); - mAsset->mBoundingBox.max = max(mAsset->mBoundingBox.max, transformed.max); + fAsset->mBoundingBox.min = min(fAsset->mBoundingBox.min, transformed.min); + fAsset->mBoundingBox.max = max(fAsset->mBoundingBox.max, transformed.max); } -void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node* node, - Entity entity, const char* name, FFilamentInstance* instance) { +void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const char* name, + FFilamentAsset* fAsset) { + const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy; const cgltf_mesh* mesh = node->mesh; const cgltf_size primitiveCount = mesh->primitives_count; // If the mesh is already loaded, obtain the list of Filament VertexBuffer / IndexBuffer objects // that were already generated (one for each primitive). - FixedCapacityVector& prims = mAsset->mMeshCache[mesh - srcAsset->meshes]; + FixedCapacityVector& prims = fAsset->mMeshCache[mesh - srcAsset->meshes]; assert_invariant(prims.size() == primitiveCount); Primitive* outputPrim = prims.data(); const cgltf_primitive* inputPrim = &mesh->primitives[0]; @@ -698,15 +680,15 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node // Create a material instance for this primitive or fetch one from the cache. UvMap uvmap {}; bool hasVertexColor = primitiveHasVertexColor(*inputPrim); - MaterialInstance* mi = createMaterialInstance(srcAsset, inputPrim->material, &uvmap, - hasVertexColor); + MaterialInstance* mi = createMaterialInstance(inputPrim->material, &uvmap, hasVertexColor, + fAsset); assert_invariant(mi); if (!mi) { mError = true; continue; } - mAsset->mDependencyGraph.addEdge(entity, mi); + fAsset->mDependencyGraph.addEdge(entity, mi); builder.material(index, mi); assert_invariant(outputPrim->vertices); @@ -770,8 +752,8 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node } } -void FAssetLoader::createMaterialVariants(const cgltf_data* srcAsset, const cgltf_mesh* mesh, - Entity entity, FFilamentInstance* instance) { +void FAssetLoader::createMaterialVariants(const cgltf_mesh* mesh, Entity entity, + FFilamentAsset* fAsset, FFilamentInstance* instance) { UvMap uvmap {}; for (cgltf_size prim = 0, n = mesh->primitives_count; prim < n; ++prim) { const cgltf_primitive& srcPrim = mesh->primitives[prim]; @@ -780,21 +762,22 @@ void FAssetLoader::createMaterialVariants(const cgltf_data* srcAsset, const cglt const cgltf_material* material = srcPrim.mappings[i].material; bool hasVertexColor = primitiveHasVertexColor(srcPrim); MaterialInstance* mi = - createMaterialInstance(srcAsset, material, &uvmap, hasVertexColor); + createMaterialInstance(material, &uvmap, hasVertexColor, fAsset); assert_invariant(mi); if (!mi) { mError = true; break; } - mAsset->mDependencyGraph.addEdge(entity, mi); + fAsset->mDependencyGraph.addEdge(entity, mi); instance->mVariants[variantIndex].mappings.push_back({entity, prim, mi}); } } } -bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* outPrim, - const char* name) { - Material* material = getMaterial(mAsset->mSourceAsset->hierarchy, +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(); @@ -804,7 +787,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out // request from Google. // Create a little lambda that appends to the asset's vertex buffer slots. - auto slots = &mAsset->mBufferSlots; + auto slots = &fAsset->mBufferSlots; auto addBufferSlot = [slots](BufferSlot entry) { slots->push_back(entry); }; @@ -844,7 +827,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out IndexBuffer::BufferDescriptor bd(indexData, indexDataSize, FREE_CALLBACK); indices->setBuffer(mEngine, std::move(bd)); } - mAsset->mIndexBuffers.push_back(indices); + fAsset->mIndexBuffers.push_back(indices); VertexBuffer::Builder vbb; vbb.enableBufferObjects(); @@ -852,7 +835,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out bool hasUv0 = false, hasUv1 = false, hasVertexColor = false, hasNormals = false; uint32_t vertexCount = 0; - const size_t firstSlot = mAsset->mBufferSlots.size(); + const size_t firstSlot = slots->size(); int slot = 0; for (cgltf_size aindex = 0; aindex < inPrim.attributes_count; aindex++) { @@ -872,7 +855,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4); vbb.normalized(VertexAttribute::TANGENTS); hasNormals = true; - addBufferSlot({&mAsset->mGenerateTangents, atype, slot++}); + addBufferSlot({&fAsset->mGenerateTangents, atype, slot++}); continue; } @@ -894,6 +877,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out 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; @@ -958,7 +942,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4); vbb.normalized(VertexAttribute::TANGENTS); cgltf_attribute_type atype = cgltf_attribute_type_normal; - addBufferSlot({&mAsset->mGenerateNormals, atype, slot++}); + addBufferSlot({&fAsset->mGenerateNormals, atype, slot++}); } cgltf_size targetsCount = inPrim.targets_count; @@ -1065,11 +1049,11 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out outPrim->indices = indices; outPrim->vertices = vertices; - mAsset->mPrimitives.push_back({&inPrim, vertices}); - mAsset->mVertexBuffers.push_back(vertices); + fAsset->mPrimitives.push_back({&inPrim, vertices}); + fAsset->mVertexBuffers.push_back(vertices); - for (size_t i = firstSlot; i < mAsset->mBufferSlots.size(); ++i) { - mAsset->mBufferSlots[i].vertexBuffer = vertices; + for (size_t i = firstSlot; i < slots->size(); ++i) { + (*slots)[i].vertexBuffer = vertices; } if (targetsCount > 0) { @@ -1078,8 +1062,8 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out .count(targetsCount) .build(mEngine); outPrim->targets = targets; - mAsset->mMorphTargetBuffers.push_back(targets); - const cgltf_accessor* previous = nullptr; + fAsset->mMorphTargetBuffers.push_back(targets); + UTILS_UNUSED_IN_RELEASE cgltf_accessor const* previous = nullptr; for (int tindex = 0; tindex < targetsCount; ++tindex) { const cgltf_morph_target& inTarget = inPrim.targets[tindex]; for (cgltf_size aindex = 0; aindex < inTarget.attributes_count; ++aindex) { @@ -1104,7 +1088,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out const uint32_t requiredSize = sizeof(ubyte4) * vertexCount; if (mDummyBufferObject == nullptr || requiredSize > mDummyBufferObject->getByteCount()) { mDummyBufferObject = BufferObject::Builder().size(requiredSize).build(mEngine); - mAsset->mBufferObjects.push_back(mDummyBufferObject); + fAsset->mBufferObjects.push_back(mDummyBufferObject); uint32_t* dummyData = (uint32_t*) malloc(requiredSize); memset(dummyData, 0xff, requiredSize); VertexBuffer::BufferDescriptor bd(dummyData, requiredSize, FREE_CALLBACK); @@ -1116,7 +1100,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out return true; } -void FAssetLoader::createLight(const cgltf_light* light, Entity entity) { +void FAssetLoader::createLight(const cgltf_light* light, Entity entity, FFilamentAsset* fAsset) { LightManager::Type type = getLightType(light->type); LightManager::Builder builder(type); @@ -1149,10 +1133,10 @@ void FAssetLoader::createLight(const cgltf_light* light, Entity entity) { } builder.build(mEngine, entity); - mAsset->mLightEntities.push_back(entity); + fAsset->mLightEntities.push_back(entity); } -void FAssetLoader::createCamera(const cgltf_camera* camera, Entity entity) { +void FAssetLoader::createCamera(const cgltf_camera* camera, Entity entity, FFilamentAsset* fAsset) { Camera* filamentCamera = mEngine.createCamera(entity); if (camera->type == cgltf_camera_type_perspective) { @@ -1187,7 +1171,7 @@ void FAssetLoader::createCamera(const cgltf_camera* camera, Entity entity) { return; } - mAsset->mCameraEntities.push_back(entity); + fAsset->mCameraEntities.push_back(entity); } MaterialKey FAssetLoader::getMaterialKey(const cgltf_data* srcAsset, @@ -1301,8 +1285,9 @@ Material* FAssetLoader::getMaterial(const cgltf_data* srcAsset, return material; } -MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsset, - const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor) { +MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_material* inputMat, UvMap* uvmap, + bool vertexColor, FFilamentAsset* fAsset) { + const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy; MaterialInstanceCache::Entry* const cacheEntry = mMaterialInstanceCache.getEntry(&inputMat, vertexColor); if (cacheEntry->instance) { @@ -1368,7 +1353,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse const TextureProvider::TextureFlags LINEAR = TextureProvider::TextureFlags::NONE; if (matkey.hasBaseColorTexture) { - mAsset->addTextureBinding(mi, "baseColorMap", baseColorTexture.texture, sRGB); + fAsset->addTextureBinding(mi, "baseColorMap", baseColorTexture.texture, sRGB); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = baseColorTexture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); @@ -1382,7 +1367,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse // specularGlossinessTexture are both sRGB, whereas the core glTF spec stipulates that // metallicRoughness is not sRGB. TextureProvider::TextureFlags srgb = inputMat->has_pbr_specular_glossiness ? sRGB : LINEAR; - mAsset->addTextureBinding(mi, "metallicRoughnessMap", metallicRoughnessTexture.texture, srgb); + fAsset->addTextureBinding(mi, "metallicRoughnessMap", metallicRoughnessTexture.texture, srgb); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = metallicRoughnessTexture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); @@ -1391,7 +1376,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse } if (matkey.hasNormalTexture) { - mAsset->addTextureBinding(mi, "normalMap", inputMat->normal_texture.texture, LINEAR); + fAsset->addTextureBinding(mi, "normalMap", inputMat->normal_texture.texture, LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = inputMat->normal_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); @@ -1403,7 +1388,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse } if (matkey.hasOcclusionTexture) { - mAsset->addTextureBinding(mi, "occlusionMap", inputMat->occlusion_texture.texture, LINEAR); + fAsset->addTextureBinding(mi, "occlusionMap", inputMat->occlusion_texture.texture, LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = inputMat->occlusion_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); @@ -1415,7 +1400,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse } if (matkey.hasEmissiveTexture) { - mAsset->addTextureBinding(mi, "emissiveMap", inputMat->emissive_texture.texture, sRGB); + fAsset->addTextureBinding(mi, "emissiveMap", inputMat->emissive_texture.texture, sRGB); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = inputMat->emissive_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); @@ -1428,7 +1413,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse mi->setParameter("clearCoatRoughnessFactor", ccConfig.clearcoat_roughness_factor); if (matkey.hasClearCoatTexture) { - mAsset->addTextureBinding(mi, "clearCoatMap", ccConfig.clearcoat_texture.texture, + fAsset->addTextureBinding(mi, "clearCoatMap", ccConfig.clearcoat_texture.texture, LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = ccConfig.clearcoat_texture.transform; @@ -1437,7 +1422,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse } } if (matkey.hasClearCoatRoughnessTexture) { - mAsset->addTextureBinding(mi, "clearCoatRoughnessMap", + fAsset->addTextureBinding(mi, "clearCoatRoughnessMap", ccConfig.clearcoat_roughness_texture.texture, LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = ccConfig.clearcoat_roughness_texture.transform; @@ -1446,7 +1431,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse } } if (matkey.hasClearCoatNormalTexture) { - mAsset->addTextureBinding(mi, "clearCoatNormalMap", + fAsset->addTextureBinding(mi, "clearCoatNormalMap", ccConfig.clearcoat_normal_texture.texture, LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = ccConfig.clearcoat_normal_texture.transform; @@ -1463,7 +1448,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse mi->setParameter("sheenRoughnessFactor", shConfig.sheen_roughness_factor); if (matkey.hasSheenColorTexture) { - mAsset->addTextureBinding(mi, "sheenColorMap", shConfig.sheen_color_texture.texture, + fAsset->addTextureBinding(mi, "sheenColorMap", shConfig.sheen_color_texture.texture, sRGB); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = shConfig.sheen_color_texture.transform; @@ -1473,7 +1458,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse } if (matkey.hasSheenRoughnessTexture) { bool sameTexture = shConfig.sheen_color_texture.texture == shConfig.sheen_roughness_texture.texture; - mAsset->addTextureBinding(mi, "sheenRoughnessMap", + fAsset->addTextureBinding(mi, "sheenRoughnessMap", shConfig.sheen_roughness_texture.texture, sameTexture ? sRGB : LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = shConfig.sheen_roughness_texture.transform; @@ -1494,7 +1479,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse mi->setParameter("volumeAbsorption", RgbType::LINEAR, absorption); if (matkey.hasVolumeThicknessTexture) { - mAsset->addTextureBinding(mi, "volumeThicknessMap", vlConfig.thickness_texture.texture, + fAsset->addTextureBinding(mi, "volumeThicknessMap", vlConfig.thickness_texture.texture, LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = vlConfig.thickness_texture.transform; @@ -1507,7 +1492,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse if (matkey.hasTransmission) { mi->setParameter("transmissionFactor", trConfig.transmission_factor); if (matkey.hasTransmissionTexture) { - mAsset->addTextureBinding(mi, "transmissionMap", trConfig.transmission_texture.texture, + fAsset->addTextureBinding(mi, "transmissionMap", trConfig.transmission_texture.texture, LINEAR); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = trConfig.transmission_texture.transform; @@ -1540,8 +1525,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse return mi; } -void FAssetLoader::importSkins(FFilamentAsset* primary, - FFilamentInstance* instance, const cgltf_data* gltf) { +void FAssetLoader::importSkins(FFilamentInstance* instance, const cgltf_data* gltf) { instance->mSkins.reserve(gltf->skins_count); instance->mSkins.resize(gltf->skins_count); const auto& nodeMap = instance->mNodeMap; diff --git a/libs/gltfio/src/DracoCache.cpp b/libs/gltfio/src/DracoCache.cpp index b0f061a1a1f..871e0054491 100644 --- a/libs/gltfio/src/DracoCache.cpp +++ b/libs/gltfio/src/DracoCache.cpp @@ -20,6 +20,7 @@ #include #endif +#include #include #if GLTFIO_DRACO_SUPPORTED @@ -65,7 +66,7 @@ DracoMesh::~DracoMesh() { } // Gets the number of components in the given cgltf vector type, or -1 for matrices. -static int getNumComponents(cgltf_type ctype) { +UTILS_UNUSED_IN_RELEASE static int getNumComponents(cgltf_type ctype) { return ((int) ctype) <= 4 ? ((int) ctype) : -1; } diff --git a/libs/gltfio/src/FFilamentAsset.h b/libs/gltfio/src/FFilamentAsset.h index 245e5f0f05f..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 @@ -71,7 +72,7 @@ namespace utils { namespace filament::gltfio { -class Wireframe; +struct Wireframe; // Encapsulates VertexBuffer::setBufferAt() or IndexBuffer::setBuffer(). struct BufferSlot { @@ -290,6 +291,9 @@ struct FFilamentAsset : public FilamentAsset { using SourceHandle = std::shared_ptr; SourceHandle mSourceAsset; + // The mapping of root nodes to scene membership sets. + tsl::robin_map mRootNodes; + // Stores all information related to a single cgltf_texture. // Note that more than one cgltf_texture can map to a single Filament texture, // e.g. if several have the same URL or bufferView. For each Filament texture, diff --git a/libs/gltfio/src/FNodeManager.h b/libs/gltfio/src/FNodeManager.h index 5f8dc53edb7..4e1c80d8877 100644 --- a/libs/gltfio/src/FNodeManager.h +++ b/libs/gltfio/src/FNodeManager.h @@ -57,13 +57,15 @@ class UTILS_PRIVATE FNodeManager : public NodeManager { } void destroy(utils::Entity e) noexcept { - if (Instance ci = mManager.getInstance(e); ci) { + if (Instance const ci = mManager.getInstance(e); ci) { mManager.removeComponent(e); } } void gc(utils::EntityManager& em) noexcept { - mManager.gc(em); + mManager.gc(em, [this](Entity e) { + destroy(e); + }); } void setMorphTargetNames(Instance ci, utils::FixedCapacityVector names) noexcept { diff --git a/libs/gltfio/src/FTrsTransformManager.h b/libs/gltfio/src/FTrsTransformManager.h index c53342fcdb6..a2cb53e06e5 100644 --- a/libs/gltfio/src/FTrsTransformManager.h +++ b/libs/gltfio/src/FTrsTransformManager.h @@ -70,13 +70,15 @@ class UTILS_PRIVATE FTrsTransformManager : public TrsTransformManager { } void destroy(utils::Entity e) noexcept { - if (Instance ci = mManager.getInstance(e); ci) { + if (Instance const ci = mManager.getInstance(e); ci) { mManager.removeComponent(e); } } void gc(utils::EntityManager& em) noexcept { - mManager.gc(em); + mManager.gc(em, [this](Entity e) { + destroy(e); + }); } void setTranslation(Instance ci, const float3& translation) noexcept { diff --git a/libs/gltfio/src/JitShaderProvider.cpp b/libs/gltfio/src/JitShaderProvider.cpp index 7855d8b9056..b42acda5201 100644 --- a/libs/gltfio/src/JitShaderProvider.cpp +++ b/libs/gltfio/src/JitShaderProvider.cpp @@ -330,7 +330,9 @@ Material* createMaterial(Engine* engine, const MaterialKey& config, const UvMap& MaterialBuilder::TransparencyMode::TWO_PASSES_TWO_SIDES : MaterialBuilder::TransparencyMode::DEFAULT) .reflectionMode(MaterialBuilder::ReflectionMode::SCREEN_SPACE) - .targetApi(filamat::targetApiFromBackend(engine->getBackend())); + .targetApi(filamat::targetApiFromBackend(engine->getBackend())) + .stereoscopicType(engine->getConfig().stereoscopicType) + .stereoscopicEyeCount(engine->getConfig().stereoscopicEyeCount); if (!optimizeShaders) { builder.optimization(MaterialBuilder::Optimization::NONE); diff --git a/libs/gltfio/src/Ktx2Provider.cpp b/libs/gltfio/src/Ktx2Provider.cpp index 992db8d7bd8..5ceb3902aa9 100644 --- a/libs/gltfio/src/Ktx2Provider.cpp +++ b/libs/gltfio/src/Ktx2Provider.cpp @@ -116,7 +116,7 @@ Texture* Ktx2Provider::pushTexture(const uint8_t* data, size_t byteCount, } JobSystem* js = &mEngine->getJobSystem(); - item->job = jobs::createJob(*js, mDecoderRootJob, [this, item] { + item->job = jobs::createJob(*js, mDecoderRootJob, [item] { using Result = ktxreader::Ktx2Reader::Result; const bool success = Result::SUCCESS == item->async->doTranscoding(); item->transcoderState.store(success ? TranscoderState::SUCCESS : TranscoderState::ERROR); diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 0d54c780b71..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 @@ -32,6 +33,7 @@ #include +#include #include #include #include @@ -49,6 +51,7 @@ #include #include #include +#include using namespace filament; using namespace filament::math; @@ -62,27 +65,27 @@ 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 { - Impl(const ResourceConfiguration& config) : + explicit Impl(const ResourceConfiguration& config) : mEngine(config.engine), mNormalizeSkinningWeights(config.normalizeSkinningWeights), mGltfPath(config.gltfPath ? config.gltfPath : ""), mUriDataCache(std::make_shared()) {} Engine* const mEngine; - const bool mNormalizeSkinningWeights; - const std::string mGltfPath; + bool mNormalizeSkinningWeights; + std::string mGltfPath; // User-provided resource data with URI string keys, populated with addResourceData(). // This is used on platforms without traditional file systems, such as Android, iOS, and WebGL. @@ -107,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 { @@ -121,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); - - 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; } @@ -314,12 +155,176 @@ 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() { delete pImpl; } +void ResourceLoader::setConfiguration(const ResourceConfiguration& config) { + pImpl->mNormalizeSkinningWeights = config.normalizeSkinningWeights; + pImpl->mGltfPath = config.gltfPath; +} + void ResourceLoader::addResourceData(const char* uri, BufferDescriptor&& buffer) { pImpl->addResourceData(uri, std::move(buffer)); } @@ -382,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); } @@ -404,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. - - SYSTRACE_NAME_BEGIN("Load buffers"); - #if !GLTFIO_USE_FILESYSTEM - - struct Closure { - Impl* impl; - const cgltf_data* gltf; - }; - - 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; - } + cgltf_data const* gltf = asset->mSourceAsset->hierarchy; - // If the buffer slot does not have an associated VertexBuffer or IndexBuffer, then this - // must be a morph target. - assert(slot.morphTargetBuffer); + utility::loadCgltfBuffers(gltf, pImpl->mGltfPath.c_str(), pImpl->mUriDataCache); - 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(); } @@ -895,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/StbProvider.cpp b/libs/gltfio/src/StbProvider.cpp index 17f84b76e16..c5900fd426f 100644 --- a/libs/gltfio/src/StbProvider.cpp +++ b/libs/gltfio/src/StbProvider.cpp @@ -126,7 +126,7 @@ Texture* StbProvider::pushTexture(const uint8_t* data, size_t byteCount, } JobSystem* js = &mEngine->getJobSystem(); - info->decoderJob = jobs::createJob(*js, mDecoderRootJob, [this, info] { + info->decoderJob = jobs::createJob(*js, mDecoderRootJob, [info] { auto& source = info->sourceBuffer; int width, height, comp; diff --git a/libs/gltfio/src/UbershaderProvider.cpp b/libs/gltfio/src/UbershaderProvider.cpp index 8196273f232..7e184f648d4 100644 --- a/libs/gltfio/src/UbershaderProvider.cpp +++ b/libs/gltfio/src/UbershaderProvider.cpp @@ -338,6 +338,7 @@ const char* toString(BlendingMode blendingMode) noexcept { case BlendingMode::FADE: return "fade"; case BlendingMode::MULTIPLY: return "multiply"; case BlendingMode::SCREEN: return "screen"; + case BlendingMode::CUSTOM: return "custom"; } } 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/gltfio/test/gltfio_test.cpp b/libs/gltfio/test/gltfio_test.cpp index e678c62688d..44b7d99e223 100644 --- a/libs/gltfio/test/gltfio_test.cpp +++ b/libs/gltfio/test/gltfio_test.cpp @@ -43,9 +43,6 @@ using namespace backend; using namespace gltfio; using namespace utils; -constexpr uint32_t WIDTH = 64; -constexpr uint32_t HEIGHT = 64; - char const* ANIMATED_MORPH_CUBE_GLB = "AnimatedMorphCube.glb"; static std::ifstream::pos_type getFileSize(const char* filename) { @@ -178,7 +175,6 @@ do { \ TEST_F(glTFIOTest, AnimatedMorphCubeTransforms) { FilamentAsset const& morphCubeAsset = *mData[ANIMATED_MORPH_CUBE_GLB]->getAsset(); auto const& transformManager = mEngine->getTransformManager(); - auto const& renderableManager = mEngine->getRenderableManager(); Entity const* renderables = morphCubeAsset.getRenderableEntities(); EXPECT_EQ(morphCubeAsset.getRenderableEntityCount(), 1u); diff --git a/libs/iblprefilter/include/filament-iblprefilter/IBLPrefilterContext.h b/libs/iblprefilter/include/filament-iblprefilter/IBLPrefilterContext.h index 815ff613ffb..903c42589f2 100644 --- a/libs/iblprefilter/include/filament-iblprefilter/IBLPrefilterContext.h +++ b/libs/iblprefilter/include/filament-iblprefilter/IBLPrefilterContext.h @@ -88,12 +88,23 @@ class UTILS_PUBLIC IBLPrefilterContext { */ class EquirectangularToCubemap { public: + + struct Config { + bool mirror = true; //!< mirror the source horizontally + }; + /** - * Creates a EquirectangularToCubemap processor. + * Creates a EquirectangularToCubemap processor using the default Config * @param context IBLPrefilterContext to use */ explicit EquirectangularToCubemap(IBLPrefilterContext& context); + /** + * Creates a EquirectangularToCubemap processor using the provided Config + * @param context IBLPrefilterContext to use + */ + EquirectangularToCubemap(IBLPrefilterContext& context, Config const& config); + /** * Destroys all GPU resources created during initialization. */ @@ -125,6 +136,7 @@ class UTILS_PUBLIC IBLPrefilterContext { private: IBLPrefilterContext& mContext; filament::Material* mEquirectMaterial = nullptr; + Config mConfig{}; }; /** diff --git a/libs/iblprefilter/src/IBLPrefilterContext.cpp b/libs/iblprefilter/src/IBLPrefilterContext.cpp index d4020206ea6..33705b140df 100644 --- a/libs/iblprefilter/src/IBLPrefilterContext.cpp +++ b/libs/iblprefilter/src/IBLPrefilterContext.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -29,12 +30,22 @@ #include #include -#include +#include + +#include #include +#include #include -#include -#include +#include +#include + +#include +#include +#include + +#include +#include #include "generated/resources/iblprefilter_materials.h" @@ -167,13 +178,19 @@ IBLPrefilterContext& IBLPrefilterContext::operator=(IBLPrefilterContext&& rhs) n // ------------------------------------------------------------------------------------------------ IBLPrefilterContext::EquirectangularToCubemap::EquirectangularToCubemap( - IBLPrefilterContext& context) : mContext(context) { + IBLPrefilterContext& context, + IBLPrefilterContext::EquirectangularToCubemap::Config const& config) + : mContext(context), mConfig(config) { Engine& engine = mContext.mEngine; mEquirectMaterial = Material::Builder().package( IBLPREFILTER_MATERIALS_EQUIRECTTOCUBE_DATA, IBLPREFILTER_MATERIALS_EQUIRECTTOCUBE_SIZE).build(engine); } +IBLPrefilterContext::EquirectangularToCubemap::EquirectangularToCubemap( + IBLPrefilterContext& context) : EquirectangularToCubemap(context, {}) { +} + IBLPrefilterContext::EquirectangularToCubemap::~EquirectangularToCubemap() noexcept { Engine& engine = mContext.mEngine; engine.destroy(mEquirectMaterial); @@ -217,7 +234,7 @@ Texture* IBLPrefilterContext::EquirectangularToCubemap::operator()( "equirect must be a 2D texture."); UTILS_UNUSED_IN_RELEASE - const uint8_t maxLevelCount = uint8_t(std::log2(equirect->getWidth()) + 0.5f) + 1u; + const uint8_t maxLevelCount = std::max(1, std::ilogbf(float(equirect->getWidth())) + 1); ASSERT_PRECONDITION(equirect->getLevels() == maxLevelCount, "equirect must have %u mipmap levels allocated.", +maxLevelCount); @@ -257,6 +274,8 @@ Texture* IBLPrefilterContext::EquirectangularToCubemap::operator()( .texture(RenderTarget::AttachmentPoint::COLOR1, outCube) .texture(RenderTarget::AttachmentPoint::COLOR2, outCube); + mi->setParameter("mirror", mConfig.mirror ? -1.0f : 1.0f); + for (size_t i = 0; i < 2; i++) { mi->setParameter("side", i == 0 ? 1.0f : -1.0f); diff --git a/libs/iblprefilter/src/materials/equirectToCube.mat b/libs/iblprefilter/src/materials/equirectToCube.mat index b35a869b981..36b20e21e23 100644 --- a/libs/iblprefilter/src/materials/equirectToCube.mat +++ b/libs/iblprefilter/src/materials/equirectToCube.mat @@ -10,6 +10,11 @@ material { type : float, name : side, precision: medium + }, + { + type : float, + name : mirror, + precision: medium } ], outputs : [ @@ -48,7 +53,7 @@ fragment { void dummy() {} highp vec2 toEquirect(const highp vec3 s) { - highp float xf = atan2(s.x, s.z) * (1.0 / PI); // range [-1.0, 1.0] + highp float xf = atan(s.x, s.z) * (1.0 / PI); // range [-1.0, 1.0] highp float yf = asin(s.y) * (2.0 / PI); // range [-1.0, 1.0] xf = (xf + 1.0) * 0.5; // range [0, 1.0] yf = (1.0 - yf) * 0.5; // range [0, 1.0] @@ -62,17 +67,22 @@ mediump vec3 sampleEquirect(mediump sampler2D equirect, const highp vec3 r) { void postProcess(inout PostProcessInputs postProcess) { highp vec2 uv = variable_vertex.xy; // interpolated at pixel's center - highp vec2 p = uv * 2.0 - 1.0; + highp vec2 p = vec2( + uv.x * 2.0 - 1.0, + 1.0 - uv.y * 2.0); + float side = materialParams.side; + float mirror = materialParams.mirror; + highp float l = inversesqrt(p.x * p.x + p.y * p.y + 1.0); // compute the view (and normal, since v = n) direction for each face - highp vec3 rx = normalize(vec3( side, -p.y, side * -p.x)); - highp vec3 ry = normalize(vec3( p.x, side, side * p.y)); - highp vec3 rz = normalize(vec3(side * p.x, -p.y, side)); + highp vec3 rx = vec3( side * mirror, p.y, side * -p.x); + highp vec3 ry = vec3( p.x * mirror, side, side * -p.y); + highp vec3 rz = vec3(side * p.x * mirror, p.y, side); - postProcess.outx = sampleEquirect(materialParams_equirect, rx); - postProcess.outy = sampleEquirect(materialParams_equirect, ry); - postProcess.outz = sampleEquirect(materialParams_equirect, rz); + postProcess.outx = sampleEquirect(materialParams_equirect, rx * l); + postProcess.outy = sampleEquirect(materialParams_equirect, ry * l); + postProcess.outz = sampleEquirect(materialParams_equirect, rz * l); } } diff --git a/libs/iblprefilter/src/materials/iblprefilter.mat b/libs/iblprefilter/src/materials/iblprefilter.mat index d9fa379a8e0..aa0c25c1d67 100644 --- a/libs/iblprefilter/src/materials/iblprefilter.mat +++ b/libs/iblprefilter/src/materials/iblprefilter.mat @@ -122,9 +122,10 @@ void postProcess(inout PostProcessInputs postProcess) { float side = materialParams.side; // compute the view (and normal, since v = n) direction for each face - vec3 rx = normalize(vec3( side, -p.y, side * -p.x)); - vec3 ry = normalize(vec3( p.x, side, side * p.y)); - vec3 rz = normalize(vec3(side * p.x, -p.y, side)); + float l = inversesqrt(p.x * p.x + p.y * p.y + 1.0); + vec3 rx = vec3( side, p.y, side * -p.x) * l; + vec3 ry = vec3( p.x, side, side * -p.y) * l; + vec3 rz = vec3(side * p.x, p.y, side) * l; // random rotation around r mediump float a = 2.0 * PI * random(gl_FragCoord.xy); diff --git a/libs/matdbg/CMakeLists.txt b/libs/matdbg/CMakeLists.txt index 0dafe5c71dd..e5805854808 100644 --- a/libs/matdbg/CMakeLists.txt +++ b/libs/matdbg/CMakeLists.txt @@ -41,8 +41,8 @@ set(SRCS set(RESOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(RESOURCE_BINS - ${CMAKE_CURRENT_SOURCE_DIR}/web/style.css - ${CMAKE_CURRENT_SOURCE_DIR}/web/script.js + ${CMAKE_CURRENT_SOURCE_DIR}/web/api.js + ${CMAKE_CURRENT_SOURCE_DIR}/web/app.js ${CMAKE_CURRENT_SOURCE_DIR}/web/index.html ) diff --git a/libs/matdbg/src/ApiHandler.cpp b/libs/matdbg/src/ApiHandler.cpp index 6a7d801e121..6de0e4ad1cf 100644 --- a/libs/matdbg/src/ApiHandler.cpp +++ b/libs/matdbg/src/ApiHandler.cpp @@ -31,10 +31,12 @@ #include #include +#include namespace filament::matdbg { using namespace filament::backend; +using namespace std::chrono_literals; namespace { @@ -213,7 +215,7 @@ bool ApiHandler::handleGetApiShader(struct mg_connection* conn, } void ApiHandler::addMaterial(MaterialRecord const* material) { - std::unique_lock lock(mStatusMutex); + std::unique_lock const lock(mStatusMutex); snprintf(statusMaterialId, sizeof(statusMaterialId), "%8.8x", material->key); mStatusCondition.notify_all(); } @@ -227,14 +229,17 @@ bool ApiHandler::handleGetStatus(struct mg_connection* conn, return true; } - { - std::unique_lock lock(mStatusMutex); - uint64_t const currentStatusCount = mCurrentStatus; - mStatusCondition.wait(lock, - [this, currentStatusCount] { return currentStatusCount < mCurrentStatus; }); - + std::unique_lock lock(mStatusMutex); + uint64_t const currentStatusCount = mCurrentStatus; + if (mStatusCondition.wait_for(lock, 10s, + [this, currentStatusCount] { return currentStatusCount < mCurrentStatus; })) { mg_printf(conn, kSuccessHeader.data(), "application/txt"); mg_write(conn, statusMaterialId, 8); + } else { + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + // Use '1' to indicate a no-op. This ensures that we don't block forever if the client is + // gone. + mg_write(conn, "1", 1); } return true; } @@ -253,13 +258,13 @@ bool ApiHandler::handlePost(CivetServer* server, struct mg_connection* conn) { // [material id] [api index] [shader index] [shader source....] if (uri == "/api/edit") { struct mg_request_info const* req_info = mg_get_request_info(conn); - size_t msgLen = req_info->content_length; + size_t const msgLen = req_info->content_length; char buf[1024]; size_t readLen = 0; std::stringstream sstream; while (readLen < msgLen) { - int res = mg_read(conn, buf, sizeof(buf)); + int const res = mg_read(conn, buf, sizeof(buf)); if (res < 0) { utils::slog.e << "civet error parsing /api/edit body: " << res << utils::io::endl; break; @@ -274,7 +279,7 @@ bool ApiHandler::handlePost(CivetServer* server, struct mg_connection* conn) { int api; int shaderIndex; sstream >> std::hex >> matid >> std::dec >> api >> shaderIndex; - std::string shader = sstream.str().substr(sstream.tellg()); + std::string const shader = sstream.str().substr(sstream.tellg()); mServer->handleEditCommand(matid, backend::Backend(api), shaderIndex, shader.c_str(), shader.size()); @@ -293,7 +298,7 @@ bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) { mServer->updateActiveVariants(); // Careful not to lock the above line. - std::unique_lock lock(mServer->mMaterialRecordsMutex); + std::unique_lock const lock(mServer->mMaterialRecordsMutex); mg_printf(conn, kSuccessHeader.data(), "application/json"); mg_printf(conn, "{"); @@ -324,7 +329,7 @@ bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) { } if (uri == "/api/matids") { - std::unique_lock lock(mServer->mMaterialRecordsMutex); + std::unique_lock const lock(mServer->mMaterialRecordsMutex); mg_printf(conn, kSuccessHeader.data(), "application/json"); mg_printf(conn, "["); int index = 0; @@ -337,7 +342,7 @@ bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) { } if (uri == "/api/materials") { - std::unique_lock lock(mServer->mMaterialRecordsMutex); + std::unique_lock const lock(mServer->mMaterialRecordsMutex); mg_printf(conn, kSuccessHeader.data(), "application/json"); mg_printf(conn, "["); int index = 0; diff --git a/libs/matdbg/src/ApiHandler.h b/libs/matdbg/src/ApiHandler.h index 27464d56267..271b5243fa2 100644 --- a/libs/matdbg/src/ApiHandler.h +++ b/libs/matdbg/src/ApiHandler.h @@ -17,7 +17,8 @@ #ifndef MATDBG_APIHANDLER_H #define MATDBG_APIHANDLER_H -#include +#include +#include #include @@ -53,14 +54,14 @@ class ApiHandler : public CivetHandler { DebugServer* mServer; - utils::Mutex mStatusMutex; + std::mutex mStatusMutex; std::condition_variable mStatusCondition; char statusMaterialId[9] = {}; // This variable is to implement a *hanging* effect for /api/status. The call to /api/status // will always block until statusMaterialId is updated again. The client is expected to keep // calling /api/status (a constant "pull" to simulate a push). - std::atomic_uint64_t mCurrentStatus = 0; + std::atomic mCurrentStatus = 0; }; } // filament::matdbg diff --git a/libs/matdbg/src/CommonWriter.h b/libs/matdbg/src/CommonWriter.h index 6d2afc1ee4e..9e49df9a862 100644 --- a/libs/matdbg/src/CommonWriter.h +++ b/libs/matdbg/src/CommonWriter.h @@ -65,6 +65,7 @@ const char* toString(BlendingMode blendingMode) noexcept { case BlendingMode::FADE: return "fade"; case BlendingMode::MULTIPLY: return "multiply"; case BlendingMode::SCREEN: return "screen"; + case BlendingMode::CUSTOM: return "custom"; } return "--"; } diff --git a/libs/matdbg/src/DebugServer.cpp b/libs/matdbg/src/DebugServer.cpp index 99e8fe52a33..2416931e665 100644 --- a/libs/matdbg/src/DebugServer.cpp +++ b/libs/matdbg/src/DebugServer.cpp @@ -50,9 +50,28 @@ using utils::FixedCapacityVector; // serves files directly from the source code tree. #define SERVE_FROM_SOURCE_TREE 0 -#if !SERVE_FROM_SOURCE_TREE +#if SERVE_FROM_SOURCE_TREE + +namespace { +std::string const BASE_URL = "libs/matdbg/web"; +} // anonymous + +#else + #include "matdbg_resources.h" -#endif +#include + +namespace { + +struct Asset { + std::string_view mime; + std::string_view data; +}; +std::unordered_map ASSET_MAP; + +} // anonymous + +#endif // SERVE_FROM_SOURCE_TREE namespace filament::matdbg { @@ -70,45 +89,32 @@ std::string_view const DebugServer::kErrorHeader = "HTTP/1.1 404 Not Found\r\nContent-Type: %s\r\n" "Connection: close\r\n\r\n"; -namespace { - -}// namespace - class FileRequestHandler : public CivetHandler { public: FileRequestHandler(DebugServer* server) : mServer(server) {} bool handleGet(CivetServer *server, struct mg_connection *conn) { auto const& kSuccessHeader = DebugServer::kSuccessHeader; - - const struct mg_request_info* request = mg_get_request_info(conn); + struct mg_request_info const* request = mg_get_request_info(conn); std::string uri(request->request_uri); - if (uri == "/" || uri == "/index.html") { - #if SERVE_FROM_SOURCE_TREE - mg_send_file(conn, "libs/matdbg/web/index.html"); - #else - mg_printf(conn, kSuccessHeader.data(), "text/html"); - mg_write(conn, mServer->mHtml.c_str(), mServer->mHtml.size()); - #endif - return true; + if (uri == "/") { + uri = "/index.html"; } - if (uri == "/style.css") { - #if SERVE_FROM_SOURCE_TREE - mg_send_file(conn, "libs/matdbg/web/style.css"); - #else - mg_printf(conn, kSuccessHeader.data(), "text/css"); - mg_write(conn, mServer->mCss.c_str(), mServer->mCss.size()); - #endif + +#if SERVE_FROM_SOURCE_TREE + if (uri == "/index.html" || uri == "/app.js" || uri == "/api.js") { + mg_send_file(conn, (BASE_URL + uri).c_str()); return true; } - if (uri == "/script.js") { - #if SERVE_FROM_SOURCE_TREE - mg_send_file(conn, "libs/matdbg/web/script.js"); - #else - mg_printf(conn, kSuccessHeader.data(), "text/javascript"); - mg_write(conn, mServer->mJavascript.c_str(), mServer->mJavascript.size()); - #endif +#else + auto const& asset_itr = ASSET_MAP.find(uri); + if (asset_itr != ASSET_MAP.end()) { + auto const& mime = asset_itr->second.mime; + auto const& data = asset_itr->second.data; + mg_printf(conn, kSuccessHeader.data(), mime.data()); + mg_write(conn, data.data(), data.size()); return true; } +#endif slog.e << "DebugServer: bad request at line " << __LINE__ << ": " << uri << io::endl; return false; } @@ -117,10 +123,20 @@ class FileRequestHandler : public CivetHandler { }; DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) { + #if !SERVE_FROM_SOURCE_TREE - mHtml = CString((const char*) MATDBG_RESOURCES_INDEX_DATA, MATDBG_RESOURCES_INDEX_SIZE - 1); - mJavascript = CString((const char*) MATDBG_RESOURCES_SCRIPT_DATA, MATDBG_RESOURCES_SCRIPT_SIZE - 1); - mCss = CString((const char*) MATDBG_RESOURCES_STYLE_DATA, MATDBG_RESOURCES_STYLE_SIZE - 1); + ASSET_MAP["/index.html"] = { + .mime = "text/html", + .data = {(char const*) MATDBG_RESOURCES_INDEX_DATA}, + }; + ASSET_MAP["/app.js"] = { + .mime = "text/javascript", + .data = {(char const*) MATDBG_RESOURCES_APP_DATA}, + }; + ASSET_MAP["/api.js"] = { + .mime = "text/javascript", + .data = {(char const*) MATDBG_RESOURCES_API_DATA}, + }; #endif // By default the server spawns 50 threads so we override this to 10. According to the civetweb diff --git a/libs/matdbg/web/api.js b/libs/matdbg/web/api.js new file mode 100644 index 00000000000..3a580fcacfe --- /dev/null +++ b/libs/matdbg/web/api.js @@ -0,0 +1,145 @@ +/* +* Copyright (C) 2023 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. +*/ + +// api.js encapsulates all of the REST endpoints that the server provides + +async function _fetchJson(uri) { + const response = await fetch(uri); + return await response.json(); +} + +async function _fetchText(uri) { + const response = await fetch(uri); + return await response.text(); +} + +async function fetchShaderCode(matid, backend, language, index) { + let query; + switch (backend) { + case "opengl": + query = `type=${language}&glindex=${index}`; + break; + case "vulkan": + query = `type=${language}&vkindex=${index}`; + break; + case "metal": + query = `type=${language}&metalindex=${index}`; + break; + } + return await _fetchText(`api/shader?matid=${matid}&${query}`); +} + +async function fetchMaterials() { + const matJson = await _fetchJson("api/materials") + const ret = {}; + for (const matInfo of matJson) { + ret[matInfo.matid] = matInfo; + } + return ret; +} + +async function fetchMaterial(matId) { + const matInfo = await _fetchJson(`api/material?matid=${matid}`); + matInfo.matid = matid; + return matInfo; +} + +async function fetchMatIds() { + const matInfo = await _fetchJson("api/matids"); + const ret = []; + for (matid of matInfo) { + ret.push(matid); + } + return ret; +} + +async function queryActiveShaders() { + const activeMaterials = await _fetchJson("api/active"); + const actives = {}; + for (matid in activeMaterials) { + const backend = activeMaterials[matid][0]; + const variants = activeMaterials[matid].slice(1); + actives[matid] = { + backend, variants + }; + } + return actives; +} + +function rebuildMaterial(materialId, backend, shaderIndex, editedText) { + let api = 0; + switch (backend) { + case "opengl": api = 1; break; + case "vulkan": api = 2; break; + case "metal": api = 3; break; + } + return new Promise((ok, fail) => { + const req = new XMLHttpRequest(); + req.open('POST', '/api/edit'); + req.send(`${materialId} ${api} ${shaderIndex} ${editedText}`); + req.onload = ok; + req.onerror = fail; + }); +} + +function activeShadersLoop(isConnected, onActiveShaders) { + setInterval(async () => { + if (isConnected()) { + onActiveShaders(await queryActiveShaders()); + } + }, 1000); +} + +const STATUS_LOOP_TIMEOUT = 3000; + +const STATUS_CONNECTED = 1; +const STATUS_DISCONNECTED = 2; +const STATUS_MATERIAL_UPDATED = 3; + +// Status function should be of the form function(status, data) +async function statusLoop(isConnected, onStatus) { + // This is a hanging get except for when transition from disconnected to connected, which + // should return immediately. + try { + const matid = await _fetchText("api/status" + (isConnected() ? '' : '?firstTime')); + // A first-time request returned successfully + if (matid === '0') { + onStatus(STATUS_CONNECTED); + } else if (matid !== '1') { + onStatus(STATUS_MATERIAL_UPDATED, matid); + } // matid == '1' is no-op, just loop again + statusLoop(isConnected, onStatus); + } catch { + onStatus(STATUS_DISCONNECTED); + setTimeout(() => statusLoop(isConnected, onStatus), STATUS_LOOP_TIMEOUT) + } +} + +// Use browser User-agent to guess the current backend. This is mainly for matinfo which does +// not have a running backend. +function guessBackend() { + const AGENTS_TO_BACKEND = [ + ['Mac OS', 'metal'], + ['Windows', 'opengl'], + ['Linux', 'vulkan'], + ]; + + const result = AGENTS_TO_BACKEND.filter((agent_backend) => { + return window.navigator.userAgent.search(agent_backend[0]); + }).map((agent_backend) => agent_backend[1]); + + return result.length > 0 ? result[0] : null; +} diff --git a/libs/matdbg/web/app.js b/libs/matdbg/web/app.js new file mode 100644 index 00000000000..4a7c7df4e44 --- /dev/null +++ b/libs/matdbg/web/app.js @@ -0,0 +1,1100 @@ +/* +* Copyright (C) 2023 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { LitElement, html, css, unsafeCSS, nothing } from "https://unpkg.com/lit@2.8.0?module"; + +const kUntitledPlaceholder = "untitled"; + +// Maps to backend to the languages allowed for that backend. +const LANGUAGE_CHOICES = { + 'opengl': ['glsl'], + 'vulkan': ['glsl', 'spirv'], + 'metal': ['msl'], +}; + +const BACKENDS = Object.keys(LANGUAGE_CHOICES); + +const MATERIAL_INFO_KEY_TO_STRING = { + 'model': 'shading model', + 'vertex_domain': 'vertex domain', + 'interpolation': 'interpolation', + 'shadow_multiply': 'shadow multiply', + 'specular_antialiasing': 'specular antialiasing', + 'variance': 'variance', + 'threshold': 'threshold', + 'clear_coat_IOR_change': 'clear coat IOR change', + 'blending': 'blending', + 'mask_threshold': 'mask threshold', + 'color_write': 'color write', + 'depth_write': 'depth write', + 'depth_test': 'depth test', + 'double_sided': 'double sided', + 'culling': 'culling', + 'transparency': 'transparency', +}; + +// CSS constants +const FOREGROUND_COLOR = '#fafafa'; +const INACTIVE_COLOR = '#9a9a9a'; +const DARKER_INACTIVE_COLOR = '#6f6f6f'; +const LIGHTER_INACTIVE_COLOR = '#d9d9d9'; +const UNSELECTED_COLOR = '#dfdfdf'; +const BACKGROUND_COLOR = '#5362e5'; +const HOVER_BACKGROUND_COLOR = '#b3c2ff'; +const CODE_VIEWER_BOTTOM_ROW_HEIGHT = 60; +const REGULAR_FONT_SIZE = 12; + +// Set up the Monaco editor. See also CodeViewer +const kMonacoBaseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/'; +require.config({ + paths: { "vs": `${kMonacoBaseUrl}vs` }, + 'vs/css': { disabled: true }, +}); +window.MonacoEnvironment = { + getWorkerUrl: function() { + return `data:text/javascript;charset=utf-8,${encodeURIComponent(` + self.MonacoEnvironment = { + baseUrl: '${kMonacoBaseUrl}' + }; + importScripts('${kMonacoBaseUrl}vs/base/worker/workerMain.js');` + )}`; + } +}; + +const _validDict = (obj) => { + return obj && Object.keys(obj).length > 0; +} + +const _isMatInfoMode = (database) => { + return Object.keys(database).length == 1; +} + +class Button extends LitElement { + static get styles() { + return css` + :host { + display: flex; + } + .main { + border: solid 2px ${unsafeCSS(BACKGROUND_COLOR)}; + border-radius: 5px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + height: 30px; + padding: 1px 8px; + color: ${unsafeCSS(BACKGROUND_COLOR)}; + margin: 5px 10px; + width: 100px; + } + .main:hover { + background: ${unsafeCSS(HOVER_BACKGROUND_COLOR)}; + } + .enabled { + cursor: pointer; + } + .disabled:hover { + background: ${unsafeCSS(LIGHTER_INACTIVE_COLOR)}; + } + .disabled { + color: ${unsafeCSS(INACTIVE_COLOR)}; + border: solid 2px ${unsafeCSS(INACTIVE_COLOR)}; + background: ${unsafeCSS(LIGHTER_INACTIVE_COLOR)}; + } + `; + } + static get properties() { + return { + label: {type: String, attribute: 'label'}, + enabled: {type: Boolean, attribute: 'enabled'}, + } + } + + constructor() { + super(); + this.label = ''; + this.enabled = false; + } + + _onClick(ev) { + this.dispatchEvent(new CustomEvent('button-clicked', {bubbles: true, composed: true})); + } + + render() { + let divClass = 'main'; + if (this.enabled) { + divClass += ' enabled'; + } else { + divClass += ' disabled'; + } + return html` +
    + ${this.label} +
    + `; + } +} +customElements.define("custom-button", Button); + +class CodeViewer extends LitElement { + static get styles() { + return css` + :host { + background: white; + width:100%; + padding-top: 10px; + display: flex; + flex-direction: column; + } + #editor { + width: 100%; + height: 100%; + } + #bottom-row { + width: 100%; + display: flex; + height: ${unsafeCSS(CODE_VIEWER_BOTTOM_ROW_HEIGHT)}px; + flex-direction: column; + align-items: flex-end; + justify-content: center; + border-top: solid 1px ${unsafeCSS(BACKGROUND_COLOR)}; + } + .hide { + display: none; + } + .reminder { + height: 100%; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + font-size: 20px; + color: ${unsafeCSS(BACKGROUND_COLOR)}; + } + .stateText { + color: ${unsafeCSS(INACTIVE_COLOR)}; + padding: 0 10px; + } + `; + } + + static get properties() { + return { + connected: {type: Boolean, attribute: 'connected'}, + code: {type: String, state: true}, + active: {type: Boolean, attribute: 'active'}, + modified: {type: Boolean, attribute: 'modified'}, + expectedWidth: {type: Number, attribute: 'expected-width'}, + expectedHeight: {type: Number, attribute: 'expected-height'}, + } + } + + get _editorDiv() { + return this.renderRoot.querySelector('#editor'); + } + + firstUpdated() { + const innerStyle = document.createElement('style'); + innerStyle.innerText = `@import "${kMonacoBaseUrl}/vs/editor/editor.main.css";`; + this.renderRoot.appendChild(innerStyle); + + require(["vs/editor/editor.main"], () => { + this.editor = monaco.editor.create(this._editorDiv, { + language: "cpp", + scrollBeyondLastLine: false, + readOnly: false, + minimap: { enabled: false }, + automaticLayout: true, + + // Workaround see https://github.com/microsoft/monaco-editor/issues/3217 + fontLigatures: '', + }); + const KeyMod = monaco.KeyMod, KeyCode = monaco.KeyCode; + this.editor.onDidChangeModelContent(this._onEdit.bind(this)); + this.editor.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, this._rebuild.bind(this)); + + // It might be that the code is available before the editor has been created. + if (this.code && this.code.length > 0) { + this.editor.setValue(this.code); + } + }); + } + + _onEdit(edit) { + // If the edit is the loading of the entire code, we ignore the edit. + if (edit.changes[0].text.length == this.code.length) { + return; + } + this.dispatchEvent(new CustomEvent( + 'shader-edited', + {detail: this.editor.getValue(), bubbles: true, composed: true} + )); + } + + _rebuild() { + if (!this.active || !this.modified) { + console.log('Called rebuild while variant is inactive or unmodified'); + return; + } + this.dispatchEvent(new CustomEvent( + 'rebuild-shader', + {detail: this.editor.getValue(), bubbles: true, composed: true} + )); + } + + updated(props) { + if (props.has('code') && this.code.length > 0) { + // Note that the prop might have been updated before the editor is available. + if (this.editor) { + this.editor.setValue(this.code); + } + } + if ((props.has('expectedWidth') || props.has('expectedHeight')) && + (this.expectedWidth > 0 && (this.expectedHeight - CODE_VIEWER_BOTTOM_ROW_HEIGHT) > 0)) { + const actualWidth = Math.floor(this.expectedWidth); + const actualHeight = (Math.floor(this.expectedHeight) - CODE_VIEWER_BOTTOM_ROW_HEIGHT); + this._editorDiv.style.width = actualWidth + 'px'; + this._editorDiv.style.height = actualHeight + 'px'; + } + } + + constructor() { + super(); + this.code = ''; + this.active = false; + this.modified = false; + this.addEventListener('button-clicked', this._rebuild.bind(this)); + this.expectedWidth = 0; + this.expectedHeight = 0; + } + + render() { + let divClass = ''; + let reminder = null; + if (this.code.length == 0) { + divClass += ' hide'; + reminder = (() => html`
    Please select a shader in the side panel.
    `)(); + } + let stateText = null; + if (!this.connected) { + stateText = 'disconnected'; + } else if (this.code.length > 0 && !this.active) { + stateText = 'inactive variant/shader'; + } else if (this.code.length > 0 &&!this.modified) { + stateText = 'source unmodified'; + } + + const stateDiv = stateText ? (() => html` +
    ${stateText}
    + `)(): null; + + return html` +
    + ${reminder ?? nothing} +
    +
    + ${stateDiv ?? nothing} + + +
    +
    + `; + } +} +customElements.define("code-viewer", CodeViewer); + +class MenuSection extends LitElement { + static get properties() { + return { + showing: {type: Boolean, state: true}, + title: {type: String, attribute: 'title'}, + }; + } + + static get styles() { + return css` + :host { + font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px; + color: ${unsafeCSS(UNSELECTED_COLOR)}; + } + .section-title { + font-size: 16px; + color: ${unsafeCSS(UNSELECTED_COLOR)}; + cursor: pointer; + } + .container { + margin-bottom: 20px; + } + hr { + display: block; + height: 1px; + border: 0px; + border-top: 1px solid ${unsafeCSS(UNSELECTED_COLOR)}; + padding: 0; + width: 100%; + margin: 3px 0 8px 0; + } + .expander { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + } + `; + } + + _showClick() { + this.showing = !this.showing; + } + + constructor() { + super(); + this.showing = true; + } + + render() { + const expandedIcon = this.showing ? '-' : '+'; + const slot = (() => html``)(); + return html` +
    +
    + ${this.title} ${expandedIcon} +
    +
    + ${this.showing ? slot : []} +
    + `; + } +} +customElements.define('menu-section', MenuSection); + +class MaterialInfo extends LitElement { + static get properties() { + return { + info: {type: Object, state: true}, + }; + } + + static get styles() { + return css` + :host { + font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px; + } + `; + } + + constructor() { + super(); + this.info = null; + } + + _hasInfo() { + return this.info && Object.keys(this.info).length > 0; + } + + render() { + let infoDivs = []; + if (this._hasInfo()) { + if (this.info.shading && this.info.shading.material_domain === 'surface') { + infoDivs = infoDivs.concat( + Object.keys(this.info.shading) + .filter((propKey) => (propKey in MATERIAL_INFO_KEY_TO_STRING)) + .map((propKey) => html` +
    + ${MATERIAL_INFO_KEY_TO_STRING[propKey]} = ${this.info.shading[propKey]} +
    + `) + ); + } + if (this.info.raster) { + infoDivs = infoDivs.concat( + Object.keys(this.info.raster) + .filter((propKey) => (propKey in MATERIAL_INFO_KEY_TO_STRING)) + .map((propKey) => html` +
    + ${MATERIAL_INFO_KEY_TO_STRING[propKey]} = ${this.info.raster[propKey]} +
    + `) + ); + } + } + const shouldHide = infoDivs.length == 0; + if (infoDivs.length > 0) { + return html` + + ${infoDivs} + + `; + } + return html``; + } +} +customElements.define('material-info', MaterialInfo); + +class AdvancedOptions extends LitElement { + static get properties() { + return { + currentBackend: {type: String, attribute: 'current-backend'}, + availableBackends: {type: Array, state: true}, + }; + } + + static get styles() { + return css` + :host { + font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px; + } + .option { + border: 1px solid ${unsafeCSS(UNSELECTED_COLOR)}; + border-radius: 5px; + padding: 4px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + } + label { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + margin-right: 5px; + } + label input { + margin: 0 4px 0 0; + } + form { + display: flex; + } + .option-heading { + margin-bottom: 5px; + } + `; + } + + get _backendOptionForm() { + return this.renderRoot.querySelector('#backend-option-form'); + } + + updated(props) { + if (props.has('currentBackend') || props.has('availableBackends')) { + // Clear the radio button selections. The correct option will be selected + // in _backendOption(). + if (this._backendOptionForm) { + this._backendOptionForm.reset(); + } + } + } + + _backendOption() { + if (this.availableBackends.length == 0) { + return null; + } + + const onChange = (ev) => { + const backend = ev.currentTarget.getAttribute('name'); + this.dispatchEvent( + new CustomEvent( + 'option-backend', + {detail: backend, bubbles: true, composed: true})); + } + const div = this.availableBackends.map((backend) => { + const selected = backend == this.currentBackend; + return html` + + `; + }); + + return html` +
    +
    Current Backend
    +
    + ${div} +
    +
    + `; + } + + constructor() { + super(); + this.availableBackends = []; + } + + render() { + return html` + + ${this._backendOption() ?? nothing} + + `; + } +} +customElements.define('advanced-options', AdvancedOptions); + + +class MaterialSidePanel extends LitElement { + // Setting the style in render() has poor performance implications. We use it simply to avoid + // having another container descending from the root to host the background color. + dynamicStyle() { + return ` + :host { + background: ${this.connected ? BACKGROUND_COLOR : DARKER_INACTIVE_COLOR}; + width:100%; + max-width: 250px; + min-width: 180px; + padding: 10px 20px; + overflow-y: auto; + } + .title { + color: white; + width: 100%; + text-align: center; + margin: 0 0 10px 0; + font-size: 20px; + } + .materials { + display: flex; + flex-direction: column; + margin-bottom: 20px; + font-size: ${REGULAR_FONT_SIZE}px; + color: ${UNSELECTED_COLOR}; + } + .material_variant_language:hover { + text-decoration: underline; + } + .material_variant_language { + cursor: pointer; + } + .selected { + font-weight: bolder; + color: ${FOREGROUND_COLOR}; + } + .inactive { + color: ${INACTIVE_COLOR}; + } + .variant-list { + padding-left: 20px; + } + .language { + margin: 0 8px 0 0; + } + .languages { + padding-left: 20px; + flex-direction: row; + display: flex; + } + `; + } + + static get properties() { + return { + connected: {type: Boolean, attribute: 'connected'}, + currentMaterial: {type: String, attribute: 'current-material'}, + currentShaderIndex: {type: Number, attribute: 'current-shader-index'}, + currentBackend: {type: String, attribute: 'current-backend'}, + currentLanguage: {type: String, attribute: 'current-language'}, + + database: {type: Object, state: true}, + materials: {type: Array, state: true}, + activeShaders: {type: Object, state: true}, + + variants: {type: Array, state: true}, + } + } + + get _materialInfo() { + return this.renderRoot.querySelector('#material-info'); + } + + get _advancedOptions() { + return this.renderRoot.querySelector('#advanced-options'); + } + + constructor() { + super(); + this.connected = false; + this.materials = []; + this.database = {}; + this.activeShaders = {}; + this.variants = []; + } + + updated(props) { + if (props.has('database')) { + const items = []; + + // Names need not be unique, so we display a numeric suffix for non-unique names. + // To achieve stable ordering of anonymous materials, we first sort by matid. + const labels = new Set(); + const matids = Object.keys(this.database).sort(); + const duplicatedLabels = {}; + for (const matid of matids) { + const name = this.database[matid].name || kUntitledPlaceholder; + if (labels.has(name)) { + duplicatedLabels[name] = 0; + } else { + labels.add(name); + } + } + + this.materials = matids.map((matid) => { + const material = this.database[matid]; + let name = material.name || kUntitledPlaceholder; + if (name in duplicatedLabels) { + const index = duplicatedLabels[name]; + name = `${name} (${index})`; + duplicatedLabels[name] = index + 1; + } + return { + matid: matid, + name: name, + domain: material.shading.material_domain === "surface" ? "surface" : "postpro", + active: material.active, + }; + }); + } + if (props.has('currentMaterial')) { + if (this.currentBackend && this.database && this.currentMaterial) { + const material = this.database[this.currentMaterial]; + const activeVariants = _validDict(this.activeShaders) ? this.activeShaders[this.currentMaterial].variants : []; + const materialShaders = material[this.currentBackend]; + let variants = []; + for (const [index, shader] of materialShaders.entries()) { + const active = activeVariants.indexOf(shader.variant) >= 0; + variants.push({ + active, + shader, + }); + } + this.variants = variants; + } + + if (this.currentMaterial && this.database) { + const material = this.database[this.currentMaterial]; + this._materialInfo.info = material; + + // The matinfo usecase + if (_isMatInfoMode(this.database)) { + this._advancedOptions.availableBackends = BACKENDS.filter((backend) => !!material[backend]); + } + } + } + } + + _handleMaterialClick(matid, ev) { + this.dispatchEvent(new CustomEvent('select-material', {detail: matid, bubbles: true, composed: true})); + } + + _handleVariantClick(shaderIndex, ev) { + this.dispatchEvent(new CustomEvent('select-variant', {detail: shaderIndex, bubbles: true, composed: true})); + } + + _handleLanguageClick(lang, ev) { + this.dispatchEvent(new CustomEvent('select-language', {detail: lang, bubbles: true, composed: true})); + } + + _buildLanguagesDiv(isActive) { + const languages = LANGUAGE_CHOICES[this.currentBackend]; + if (!languages || languages.length == 1) { + return null; + } + const languagesDiv = languages.map((lang) => { + const isLanguageSelected = lang === this.currentLanguage; + let divClass = + 'material_variant_language language' + + (isLanguageSelected ? ' selected' : '') + + (!isActive ? ' inactive' : ''); + const onClickLanguage = this._handleLanguageClick.bind(this, lang); + lang = (isLanguageSelected ? '● ' : '') + lang; + return html` +
    + ${lang} +
    + `; + }); + return html`
    ${languagesDiv}
    `; + } + + _buildShaderDiv(showAllShaders) { + if (!this.variants) { + return null; + } + let variants = + this.variants + .sort((a, b) => { + // Place the active variants up top. + if (a.active && !b.active) return -1; + if (b.active && !a.active) return 1; + return 0; + }) + .map((variant) => { + let divClass = 'material_variant_language'; + const shaderIndex = +variant.shader.index; + const isVariantSelected = this.currentShaderIndex === shaderIndex; + const isActive = variant.shader.active; + if (isVariantSelected) { + divClass += ' selected'; + } + if (!isActive) { + divClass += ' inactive'; + } + const onClickVariant = this._handleVariantClick.bind(this, shaderIndex); + // Handle the case where variantString is empty (default variant?) + let vstring = (variant.shader.variantString || '').trim(); + if (vstring.length > 0) { + vstring = `[${vstring}]`; + } + let languagesDiv = isVariantSelected ? this._buildLanguagesDiv(isActive) : null; + const stage = (isVariantSelected ? '● ' : '') + variant.shader.pipelineStage; + return html` +
    +
    ${stage} ${vstring}
    +
    + ${languagesDiv ?? nothing} + ` + }); + return html`
    ${variants}
    `; + } + + render() { + const sections = (title, domain) => { + const mats = this.materials.filter((m) => m.domain == domain).map((mat) => { + const material = this.database[mat.matid]; + const onClick = this._handleMaterialClick.bind(this, mat.matid); + let divClass = 'material_variant_language'; + let shaderDiv = null; + const isMaterialSelected = mat.matid === this.currentMaterial; + if (isMaterialSelected) { + divClass += ' selected'; + // If we are looking at an inactive material, show all shaders regardless. + const showAllShaders = !material.active; + shaderDiv = this._buildShaderDiv(showAllShaders); + } + if (!material.active) { + divClass += " inactive"; + } + const matName = (isMaterialSelected ? '● ' : '') + mat.name; + return html` +
    + ${matName} +
    + ${shaderDiv ?? nothing} + `; + }); + if (mats.length > 0) { + return html`${mats}`; + } + return null; + }; + let advancedOptions = null; + // Currently we only have one advanced option and it's only for when we're in matinfo + if (_isMatInfoMode(this.database)) { + advancedOptions = + (() => html` + + `)(); + } + + return html` + +
    +
    matdbg
    + ${sections("Surface", "surface") ?? nothing} + ${sections("Post-processing", "postpro") ?? nothing} + + ${advancedOptions ?? nothing} +
    + `; + } + +} +customElements.define("material-sidepanel", MaterialSidePanel); + +class MatdbgViewer extends LitElement { + static get styles() { + return css` + :host { + height: 100%; + width: 100%; + display: flex; + } + `; + } + + get _sidepanel() { + return this.renderRoot.querySelector('#sidepanel'); + } + + get _codeviewer() { + return this.renderRoot.querySelector('#code-viewer'); + } + + async init() { + const isConnected = () => this.connected; + statusLoop( + isConnected, + async (status, data) => { + this.connected = status == STATUS_CONNECTED || status == STATUS_MATERIAL_UPDATED; + + if (status == STATUS_MATERIAL_UPDATED) { + let matInfo = await fetchMaterial(matid); + this.database[matInfo.matid] = matInfo; + this.database = this.database; + } + } + ); + + activeShadersLoop( + isConnected, + (activeShaders) => { + this.activeShaders = activeShaders; + } + ); + + let materials = await fetchMaterials(); + this.database = materials; + } + + _getShader() { + if (!this.currentLanguage || this.currentShaderIndex < 0 || !this.currentBackend) { + return null; + } + const material = (this.database && this.currentMaterial) ? this.database[this.currentMaterial] : null; + if (!material) { + return null; + } + const shaders = material[this.currentBackend]; + return shaders[this.currentShaderIndex]; + } + + _onResize() { + const rect = this._sidepanel.getBoundingClientRect(); + this.codeViewerExpectedWidth = window.innerWidth - rect.width - 1; + this.codeViewerExpectedHeight = window.innerHeight; + } + + firstUpdated() { + this._onResize(); + } + + constructor() { + super(); + this.connected = false; + this.activeShaders = {}; + this.database = {}; + this.currentShaderIndex = -1; + this.currentMaterial = null; + this.currentLanguage = null; + this.currentBackend = null; + this.init(); + + this.addEventListener('select-material', + (ev) => { + this.currentMaterial = ev.detail; + } + ); + this.addEventListener('select-variant', + (ev) => { + this.currentShaderIndex = ev.detail; + } + ); + this.addEventListener('select-language', + (ev) => { + this.currentLanguage = ev.detail; + } + ); + + this.addEventListener('rebuild-shader', + (ev) => { + const shader = this._getShader(); + if (!shader) { + return + } + rebuildMaterial( + this.currentMaterial, this.currentBackend, this.currentShaderIndex, ev.detail); + + shader.modified = false; + // Trigger an update + this.database = this.database; + } + ); + + this.addEventListener('shader-edited', + (ev) => { + const shader = this._getShader(); + if (shader) { + shader.modified = true; + // Trigger an update + this.database = this.database; + } + } + ); + + this.addEventListener('option-backend', + (ev) => { + this.currentBackend = ev.detail; + } + ); + + addEventListener('resize', this._onResize.bind(this)); + } + + static get properties() { + return { + connected: {type: Boolean, state: true}, + database: {type: Object, state: true}, + activeShaders: {type: Object, state: true}, + currentLanguage: {type: String, state: true}, + currentMaterial: {type: String, state: true}, + // Each material has a list of variants compiled for it, this index tracks a position in the list. + currentShaderIndex: {type: Number, state: true}, + currentBackend: {type: String, state: true}, + codeViewerExpectedWidth: {type: Number, state: true}, + codeViewerExpectedHeight: {type: Number, state: true}, + } + } + + updated(props) { + // Set a language if there hasn't been one set. + if (props.has('currentBackend') && this.currentBackend) { + const choices = LANGUAGE_CHOICES[this.currentBackend]; + if (choices.indexOf(this.currentLanguage) < 0) { + this.currentLanguage = choices[0]; + } + } + if (props.has('currentMaterial')) { + // Try to find a default shader index + if ((this.currentMaterial in this.activeShaders) && this.currentBackend) { + const material = this.database[this.currentMaterial]; + const activeVariants = this.activeShaders[this.currentMaterial].variants; + const materialShaders = material[this.currentBackend]; + for (let shader in materialShaders) { + let ind = activeVariants.indexOf(+shader); + if (ind >= 0) { + this.currentShaderIndex = +shader; + break; + } + } + } else if (this.currentMaterial) { + const material = this.database[this.currentMaterial]; + // Just pick the first variant in this materials list. + this.currentShaderIndex = 0; + } + } + if ((props.has('currentMaterial') || props.has('currentBackend') || + props.has('currentShaderIndex') || props.has('currentLanguage')) && + (this.currentMaterial && this.currentBackend && this.currentShaderIndex >= 0&& + this.currentLanguage)) { + (async () => { + this._codeviewer.code = await fetchShaderCode( + this.currentMaterial, this.currentBackend, this.currentLanguage, + this.currentShaderIndex); + const shader = this._getShader(); + if (shader) { + shader.modified = false; + this.database = this.database; + } + + // Size of the editor will be adjusted due to the code being loaded, we try to + // fit the editor again by calling the resize signal. + setTimeout(this._onResize.bind(this), 700); + })(); + } + if (props.has('activeShaders') || props.has('database')) { + // The only active materials are the ones with active variants. + Object.values(this.database).forEach((material) => { + material.active = false; + }); + for (matid in this.activeShaders) { + if (!this.database[matid]) { + continue; + } + let material = this.database[matid]; + const backend = this.activeShaders[matid].backend; + const variants = this.activeShaders[matid].variants; + for (let shader of material[backend]) { + shader.active = variants.indexOf(shader.variant) > -1; + material.active = material.active || shader.active; + } + } + if (_validDict(this.activeShaders)) { + let backends = {}; + for (let matid in this.activeShaders) { + const backend = this.activeShaders[matid].backend; + if (backend in backends) { + backends[backend] = backends[backend] + 1; + } else { + backends[backend] = 1; + } + } + let backendList = Object.keys(backends); + if (backendList.length > 0) { + this.currentBackend = backendList[0]; + } + } else if (!this.currentBackend) { + // Make a guess on the backend if one wasn't from activeShaders. + this.currentBackend = guessBackend(); + } + + this._sidepanel.database = this.database; + this._sidepanel.activeShaders = this.activeShaders; + } + if (props.has('connected') && this.connected) { + (async () => { + for (const matId of await fetchMatIds()) { + const matInfo = await fetchMaterial(matid); + this.database[matInfo.matid] = matInfo; + this.database = this.database; + } + + // In the `matinfo -w` usecase, we assume the current material to be the only + // material available in the database. + if (_isMatInfoMode(this.database)) { + this.currentMaterial = Object.keys(this.database)[0]; + } + })(); + } + } + + render() { + const shader = this._getShader(); + return html` + + + + + `; + } +} +customElements.define("matdbg-viewer", MatdbgViewer); diff --git a/libs/matdbg/web/index.html b/libs/matdbg/web/index.html index 9474348e0c8..3bf2971cad8 100644 --- a/libs/matdbg/web/index.html +++ b/libs/matdbg/web/index.html @@ -1,96 +1,28 @@ - + Filament Debugger - - - - - - -
    matdbg
    - -
    - -
    -
    -
    - -
     
    - - - - - - - - - + + + + + + + + + + + diff --git a/libs/matdbg/web/script.js b/libs/matdbg/web/script.js deleted file mode 100644 index 9477ab05b7a..00000000000 --- a/libs/matdbg/web/script.js +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright (C) 2019 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. - -*/ -const kMonacoBaseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/'; -const kUntitledPlaceholder = "untitled"; - -const materialList = document.getElementById("material-list"); -const materialDetail = document.getElementById("material-detail"); -const header = document.querySelector("header"); -const footer = document.querySelector("footer"); -const shaderSource = document.getElementById("shader-source"); -const matDetailTemplate = document.getElementById("material-detail-template"); -const matListTemplate = document.getElementById("material-list-template"); - -const STATUS_LOOP_TIMEOUT = 3000; - -const gMaterialDatabase = {}; - -let gEditor = null; -let gCurrentMaterial = "00000000"; -let gCurrentLanguage = "glsl"; -let gCurrentShader = { matid: "00000000", glindex: 0 }; -let gEditorIsLoading = false; - -require.config({ paths: { "vs": `${kMonacoBaseUrl}vs` }}); - -window.MonacoEnvironment = { - getWorkerUrl: function() { - return `data:text/javascript;charset=utf-8,${encodeURIComponent(` - self.MonacoEnvironment = { - baseUrl: '${kMonacoBaseUrl}' - }; - importScripts('${kMonacoBaseUrl}vs/base/worker/workerMain.js');` - )}`; - } -}; - -function getShaderAPI(selection) { - if (!selection) { - selection = gCurrentShader; - } - if ("glindex" in selection) return "opengl"; - if ("vkindex" in selection) return "vulkan"; - if ("metalindex" in selection) return "metal"; - return "error"; -} - -function rebuildMaterial() { - let api = 0, index = -1; - - const shader = getShaderRecord(gCurrentShader); - const shaderApi = getShaderAPI(); - - switch (shaderApi) { - case "opengl": api = 1; index = gCurrentShader.glindex; break; - case "vulkan": api = 2; index = gCurrentShader.vkindex; break; - case "metal": api = 3; index = gCurrentShader.metalindex; break; - } - - if (shaderApi === "vulkan") { - if (gCurrentLanguage === "glsl") { - delete shader["spirv"]; - } else if (gCurrentLanguage === "spirv") { - delete shader["glsl"]; - } - } - - const editedText = shader[gCurrentLanguage]; - const req = new XMLHttpRequest(); - req.open('POST', '/api/edit'); - req.send(`${gCurrentShader.matid} ${api} ${index} ${editedText}`); -} - -document.querySelector("body").addEventListener("click", (evt) => { - const anchor = evt.target.closest("a"); - if (!anchor) { - return; - } - - // Handle selection of a material. - if (anchor.classList.contains("material")) { - selectMaterial(anchor.dataset.matid, true); - return; - } - - // Handle selection of a shader. - if (anchor.classList.contains("shader")) { - selectShader(anchor.dataset); - return; - } - - // Handle a rebuild. - if (anchor.classList.contains("rebuild")) { - rebuildMaterial(); - return; - } - - // Handle language selection. - for (const lang of "glsl spirv msl".split(" ")) { - if (anchor.classList.contains(lang)) { - gCurrentLanguage = lang; - selectShader(gCurrentShader); - return; - } - } -}); - -// Handle Ctrl+Arrow for fast keyboard navigation between shader variants and materials. Either the -// materialStep or shaderStep argument can be non-zero (not both) and they must be -1, 0, or +1. -// TODO: this function could be vastly simplified by changing the format of the shader selector. -function selectNextShader(materialStep, shaderStep) { - if (materialStep !== 0) { - const matids = getDisplayedMaterials().map(m => m.matid).filter(m => m); - const currentIndex = matids.indexOf(gCurrentMaterial); - const nextIndex = currentIndex + materialStep; - if (nextIndex >= 0 && nextIndex < matids.length) { - selectMaterial(matids[nextIndex], true); - } - return; - } - const material = gMaterialDatabase[gCurrentMaterial]; - const variants = []; - let currentIndex = 0; - for (const [index, shader] of material.opengl.entries()) { - if (index === gCurrentShader.glindex) currentIndex = variants.length; - variants.push({ matid, glindex: index }); - } - for (const [index, shader] of material.vulkan.entries()) { - if (index === gCurrentShader.vkindex) currentIndex = variants.length; - variants.push({ matid, vkindex: index }); - } - for (const [index, shader] of material.metal.entries()) { - if (index === gCurrentShader.metalindex) currentIndex = variants.length; - variants.push({ matid, metalindex: index }); - } - const nextIndex = currentIndex + shaderStep; - if (nextIndex >= 0 && nextIndex < variants.length) { - selectShader(variants[nextIndex]); - } -} - -function fetchMaterial(matid) { - fetch(`api/material?matid=${matid}`).then(function(response) { - return response.json(); - }).then(function(matInfo) { - if (matid in gMaterialDatabase) { - return; - } - matInfo.matid = matid; - gMaterialDatabase[matid] = matInfo; - renderMaterialList(); - }); -} - -function queryActiveShaders() { - if (!isConnected()) { - return; - } - fetch("api/active").then(function(response) { - return response.json(); - }).then(function(activeMaterials) { - // The only active materials are the ones with active variants. - for (matid in gMaterialDatabase) { - const material = gMaterialDatabase[matid]; - material.active = false; - } - for (matid in activeMaterials) { - const material = gMaterialDatabase[matid]; - const activeBackend = activeMaterials[matid][0]; - const activeShaders = activeMaterials[matid].slice(1); - for (const shader of material[activeBackend]) { - shader.active = activeShaders.indexOf(shader.variant) > -1; - material.active = material.active || shader.active; - } - } - renderMaterialList(); - renderMaterialDetail(); - }) - .catch(error => { - // This can occur if the JSON is invalid. - console.error(error); - }); -} - -function isConnected() { - return footer.innerText == 'connected'; -} - -function onConnected() { - footer.innerText = 'connected'; - fetch("api/matids").then(function(response) { - return response.json(); - }).then(function(matInfo) { - for (matid of matInfo) { - if (!(matid in gMaterialDatabase)) { - fetchMaterial(matid); - } - } - }); -} - -function onDisconnected() { - footer.innerText = 'not connected'; - for (matid in gMaterialDatabase) { - const material = gMaterialDatabase[matid]; - material.active = false; - for (const shader of material.opengl) shader.active = false; - for (const shader of material.vulkan) shader.active = false; - for (const shader of material.metal) shader.active = false; - } - renderMaterialList(); - renderMaterialDetail(); -} - -function statusLoop() { - // This is a hanging get except for when transition from disconnected to connected, which - // should return immediately. - fetch("api/status" + (isConnected() ? '' : '?firstTime')) - .then(async (response) => { - const matid = await response.text(); - // A first-time request returned successfully - if (matid === '0') { - onConnected(); - } else { - fetchMaterial(matid); - } - statusLoop(); - }) - .catch(err => { - onDisconnected(); - setTimeout(statusLoop, STATUS_LOOP_TIMEOUT) - }); -} - -function fetchMaterials() { - fetch("api/materials").then(function(response) { - return response.json(); - }).then(function(matJson) { - for (const matInfo of matJson) { - if (matInfo.matid in gMaterialDatabase) { - continue; - } - gMaterialDatabase[matInfo.matid] = matInfo; - } - selectMaterial(matJson[0].matid, true); - }); -} - -function fetchShader(selection, matinfo, onDone) { - let query, target, index; - switch (getShaderAPI(selection)) { - case "opengl": - index = parseInt(selection.glindex); - query = `type=${gCurrentLanguage}&glindex=${index}`; - target = matinfo.opengl[index]; - break; - case "vulkan": - index = parseInt(selection.vkindex); - query = `type=${gCurrentLanguage}&vkindex=${index}`; - target = matinfo.vulkan[index]; - break; - case "metal": - index = parseInt(selection.metalindex); - query = `type=${gCurrentLanguage}&metalindex=${index}`; - target = matinfo.metal[index]; - break; - } - fetch(`api/shader?matid=${matinfo.matid}&${query}`).then(function(response) { - return response.text(); - }).then(function(shaderText) { - target[gCurrentLanguage] = shaderText; - onDone(); - }); -} - -function getDisplayedMaterials() { - const items = []; - - // Names need not be unique, so we display a numeric suffix for non-unique names. - // To achieve stable ordering of anonymous materials, we first sort by matid. - const labels = new Set(); - const matids = Object.keys(gMaterialDatabase).sort(); - const duplicatedLabels = {}; - for (const matid of matids) { - const name = gMaterialDatabase[matid].name || kUntitledPlaceholder; - if (labels.has(name)) { - duplicatedLabels[name] = 0; - } else { - labels.add(name); - } - } - - // Build a list of objects to pass into the template string. - for (const matid of matids) { - const item = Object.assign({}, gMaterialDatabase[matid]); - item.classes = matid === gCurrentMaterial ? "current " : ""; - if (!item.active) { - item.classes += "inactive " - } - item.domain = item.shading.material_domain === "surface" ? "surface" : "postpro"; - item.is_material = true; - - const name = item.name || kUntitledPlaceholder; - if (name in duplicatedLabels) { - const index = duplicatedLabels[name]; - item.name = `${name} (${index})`; - duplicatedLabels[name] = index + 1; - } else { - item.name = name; - } - - items.push(item); - } - - // The template takes a flat list of items, so here we insert items for section headers using - // blank names, which causes them to sort to the top of their respective sections. - const sectionLabel = {"is_label": true, "name": ""}; - items.push(Object.assign({"label": "Surface materials", "domain": "surface"}, sectionLabel)); - items.push(Object.assign({"label": "PostProcess materials", "domain": "postpro"}, sectionLabel)); - - // Next, sort all materials and section headers. - items.sort((a, b) => { - if (a.domain > b.domain) return -1; - if (a.domain < b.domain) return +1; - if (a.name < b.name) return -1; - if (a.name > b.name) return +1; - return 0; - }); - return items; -} - -function renderMaterialList() { - const items = getDisplayedMaterials(); - materialList.innerHTML = Mustache.render(matListTemplate.innerHTML, { "item": items } ); -} - -function updateClassList(array, indexProperty, selectedIndex) { - for (let item of array) { - const current = parseInt(item[indexProperty]) === selectedIndex; - item.classes = current ? "current " : ""; - if (!item.active) { - item.classes += "inactive " - } - } -} - -function renderMaterialDetail() { - const mat = gMaterialDatabase[gCurrentMaterial]; - const ok = mat.matid === gCurrentShader.matid; - updateClassList(mat.opengl, "index", ok ? parseInt(gCurrentShader.glindex) : -1); - updateClassList(mat.vulkan, "index", ok ? parseInt(gCurrentShader.vkindex) : -1); - updateClassList(mat.metal, "index", ok ? parseInt(gCurrentShader.metalindex) : -1); - const item = Object.assign({}, mat); - if (item.shading.material_domain !== "surface") { - delete item.shading; - } - materialDetail.innerHTML = Mustache.render(matDetailTemplate.innerHTML, item); -} - -function getShaderRecord(selection) { - const mat = gMaterialDatabase[gCurrentMaterial]; - if (selection.glindex >= 0) return mat.opengl[parseInt(selection.glindex)]; - if (selection.vkindex >= 0) return mat.vulkan[parseInt(selection.vkindex)]; - if (selection.metalindex >= 0) return mat.metal[parseInt(selection.metalindex)]; - return null; -} - -function renderShaderStatus() { - const shader = getShaderRecord(gCurrentShader); - let statusString = ""; - if (shader) { - const glsl = "glsl " + (gCurrentLanguage === "glsl" ? "active" : ""); - const msl = "msl " + (gCurrentLanguage === "msl" ? "active" : ""); - const spirv = "spirv " + (gCurrentLanguage === "spirv" ? "active" : ""); - switch (getShaderAPI()) { - case "opengl": - statusString += `   [GLSL]`; - break; - case "metal": - statusString += `   [MSL]`; - break; - case "vulkan": - statusString += `   [GLSL]`; - statusString += `   [SPIRV]`; - break; - } - if (shader.modified && gCurrentLanguage !== "spirv") { - statusString += "   [rebuild]"; - } - if (!shader.active) { - statusString += "   selected variant is inactive "; - } - } - header.innerHTML = "matdbg" + statusString; -} - -function selectShader(selection) { - const shader = getShaderRecord(selection); - if (!shader) { - console.error("Shader not yet available.") - return; - } - - // Change the current language selection if necessary. - switch (getShaderAPI(selection)) { - case "opengl": - if (gCurrentLanguage !== "glsl") { - gCurrentLanguage = "glsl"; - } - break; - case "vulkan": - if (gCurrentLanguage !== "spirv" && gCurrentLanguage !== "glsl") { - gCurrentLanguage = "spirv"; - } - break; - case "metal": - if (gCurrentLanguage !== "msl") { - gCurrentLanguage = "msl"; - } - break; - } - - const showShaderSource = () => { - gCurrentShader = selection; - gCurrentShader.matid = gCurrentMaterial; - renderMaterialDetail(); - gEditorIsLoading = true; - gEditor.setValue(shader[gCurrentLanguage]); - gEditorIsLoading = false; - shaderSource.style.visibility = "visible"; - renderShaderStatus(); - }; - if (!shader[gCurrentLanguage]) { - const matInfo = gMaterialDatabase[gCurrentMaterial]; - fetchShader(selection, matInfo, showShaderSource); - } else { - showShaderSource(); - } -} - -function onEdit(changes) { - if (gEditorIsLoading) { - return; - } - const shader = getShaderRecord(gCurrentShader); - if (!shader) { - return; - } - if (!shader.modified) { - shader.modified = true; - renderShaderStatus(); - } - shader[gCurrentLanguage] = gEditor.getValue(); -} - -function selectMaterial(matid, selectFirstShader) { - gCurrentMaterial = matid; - renderMaterialList(); - renderMaterialDetail(); - if (selectFirstShader) { - const mat = gMaterialDatabase[gCurrentMaterial]; - const selection = { matid }; - if (mat.opengl.length > 0) selection.glindex = 0; - else if (mat.vulkan.length > 0) selection.vkindex = 0; - else if (mat.metal.length > 0) selection.metalindex = 0; - selectShader(selection); - } -} - -function init() { - require(["vs/editor/editor.main"], function () { - const KeyMod = monaco.KeyMod, KeyCode = monaco.KeyCode; - gEditor = monaco.editor.create(shaderSource, { - value: "", - language: "cpp", - scrollBeyondLastLine: false, - readOnly: false, - minimap: { enabled: false } - }); - gEditor.onDidChangeModelContent((e) => { onEdit(e.changes); }); - - gEditor.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => rebuildMaterial()); - - gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.UpArrow, () => selectNextShader(-1, 0)); - gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.DownArrow, () => selectNextShader(+1, 0)); - gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.LeftArrow, () => selectNextShader(0, -1)); - gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.RightArrow, () => selectNextShader(0, +1)); - - fetchMaterials(); - }); - - Mustache.parse(matDetailTemplate.innerHTML); - Mustache.parse(matListTemplate.innerHTML); - - statusLoop(); - - // Poll for active shaders once every second. - // Take care not to poll more frequently than the frame rate. Active variants are determined - // by the list of variants that were fetched between this query and the previous query. - setInterval(queryActiveShaders, 1000); -} - -init(); diff --git a/libs/matdbg/web/style.css b/libs/matdbg/web/style.css deleted file mode 100644 index 05e46e63640..00000000000 --- a/libs/matdbg/web/style.css +++ /dev/null @@ -1,110 +0,0 @@ -html, body, .viewport { - width: 100%; - height: 100%; - margin: 0; -} - -body { - font-family: 'Lexend Deca', sans-serif; - overflow: hidden; -} - -a, a:visited { - text-decoration: none; - color: #567; - cursor: pointer; -} - -a:hover, a.current { - font-weight: bold; - color: #07f; -} - -a.status_button { - color: #e4e682; -} - -a.status_button.active { - color: #fff; -} - -span.warning { - color: black; - background: orange; - font-size: small; -} - -a.inactive { - color: #aaa; -} - -a.inactive.current { - color: #5af; -} - -pre { - font-family: Menlo, Monaco, "Courier New", monospace; -} - -.vbox { - display: flex; - flex-direction: column; -} - -.scrollable { - position: relative; - overflow-y: scroll; -} - -.scrollable > * { - position: absolute; -} - -.squishy { - flex-grow: 1; -} - -.stretchy { - flex-grow: 1; -} - -.hbox { - display: flex; - flex-direction: row; -} - -.space-between { - justify-content: space-between; -} - -header, footer { - height: 26px; - background: cornflowerblue; - padding: 5px 0 0 5px; -} - -.main { - flex: 1; -} - -article { - flex: 5; - border-top: solid 2px; - border-bottom: solid 2px; - visibility: hidden; -} - -nav { - border: solid 2px; - font-size: 12px; - flex: 1; -} - -nav > *:first-child { - border-bottom: solid 2px; -} - -nav > * { - padding-left: 5px; - padding-right: 5px; -} diff --git a/libs/math/include/math/TQuatHelpers.h b/libs/math/include/math/TQuatHelpers.h index e2a89dc0d7d..0439b9712b4 100644 --- a/libs/math/include/math/TQuatHelpers.h +++ b/libs/math/include/math/TQuatHelpers.h @@ -58,7 +58,7 @@ class TQuatProductOperators { /* compound assignment products by a scalar */ - template + template>> constexpr QUATERNION& operator*=(U v) { QUATERNION& lhs = static_cast&>(*this); for (size_t i = 0; i < QUATERNION::size(); i++) { @@ -67,7 +67,7 @@ class TQuatProductOperators { return lhs; } - template + template>> constexpr QUATERNION& operator/=(U v) { QUATERNION& lhs = static_cast&>(*this); for (size_t i = 0; i < QUATERNION::size(); i++) { @@ -125,25 +125,25 @@ class TQuatProductOperators { * q.w*r.z + q.x*r.y - q.y*r.x + q.z*r.w); * */ - template + template>> friend inline constexpr QUATERNION> MATH_PURE operator*(QUATERNION q, U scalar) { // don't pass q by reference because we need a copy anyway - return q *= scalar; + return QUATERNION>(q *= scalar); } - template + template>> friend inline constexpr - QUATERNION> MATH_PURE operator*(T scalar, QUATERNION q) { + QUATERNION> MATH_PURE operator*(U scalar, QUATERNION q) { // don't pass q by reference because we need a copy anyway - return q *= scalar; + return QUATERNION>(q *= scalar); } - template + template>> friend inline constexpr QUATERNION> MATH_PURE operator/(QUATERNION q, U scalar) { // don't pass q by reference because we need a copy anyway - return q /= scalar; + return QUATERNION>(q /= scalar); } }; diff --git a/libs/math/include/math/mat3.h b/libs/math/include/math/mat3.h index 5ad06bdf4bc..035865fe2bb 100644 --- a/libs/math/include/math/mat3.h +++ b/libs/math/include/math/mat3.h @@ -17,15 +17,21 @@ #ifndef TNT_MATH_MAT3_H #define TNT_MATH_MAT3_H -#include #include #include #include +#include +#include #include #include #include +#include + +#include +#include + namespace filament { namespace math { // ------------------------------------------------------------------------------------- diff --git a/libs/math/tests/test_quat.cpp b/libs/math/tests/test_quat.cpp index 6a5849875e7..044a272a7e0 100644 --- a/libs/math/tests/test_quat.cpp +++ b/libs/math/tests/test_quat.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -324,3 +325,103 @@ TEST_F(QuatTest, NaN) { EXPECT_NEAR(qs[2], 0.5, 0.1); EXPECT_NEAR(qs[3], 0.5, 0.1); } + +TEST_F(QuatTest, Conversions) { + quat qd; + quatf qf; + float3 vf; + double3 vd; + double d = 0.0; + float f = 0.0f; + + static_assert(std::is_same, float>::value); + static_assert(std::is_same, double>::value); + static_assert(std::is_same, double>::value); + static_assert(std::is_same, double>::value); + + { + auto r1 = qd * d; + auto r2 = qd * f; + auto r3 = qf * d; + auto r4 = qf * f; + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + } + { + auto r1 = qd / d; + auto r2 = qd / f; + auto r3 = qf / d; + auto r4 = qf / f; + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + } + { + auto r1 = d * qd; + auto r2 = f * qd; + auto r3 = d * qf; + auto r4 = f * qf; + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + } + { + auto r1 = qd * vd; + auto r2 = qf * vd; + auto r3 = qd * vf; + auto r4 = qf * vf; + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + } + { + auto r1 = qd * qd; + auto r2 = qf * qd; + auto r3 = qd * qf; + auto r4 = qf * qf; + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + } + { + auto r1 = dot(qd, qd); + auto r2 = dot(qf, qd); + auto r3 = dot(qd, qf); + auto r4 = dot(qf, qf); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + } + { + auto r1 = cross(qd, qd); + auto r2 = cross(qf, qd); + auto r3 = cross(qd, qf); + auto r4 = cross(qf, qf); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + static_assert(std::is_same::value); + } +} + +template +struct has_divide_assign : std::false_type {}; + +template +struct has_divide_assign() /= std::declval(), void())> : std::true_type {}; + +// Static assertions to validate the availability of the /= operator for specific type +// combinations. The first static_assert checks that the quat does not have a /= operator with Foo. +// This ensures that quat does not provide an inappropriate overload that could be erroneously +// selected. +struct Foo {}; +static_assert(!has_divide_assign::value); +static_assert(has_divide_assign::value); diff --git a/libs/uberz/src/WritableArchive.cpp b/libs/uberz/src/WritableArchive.cpp index d7fface9472..09e601f8684 100644 --- a/libs/uberz/src/WritableArchive.cpp +++ b/libs/uberz/src/WritableArchive.cpp @@ -76,6 +76,8 @@ static string_view readBlendingMode(string_view cursor, BlendingMode* blending) *blending = BlendingMode::MULTIPLY; } else if (sz = readPrefix("screen"sv, cursor); sz > 0) { *blending = BlendingMode::SCREEN; + } else if (sz = readPrefix("custom"sv, cursor); sz > 0) { + *blending = BlendingMode::CUSTOM; } return { cursor.data(), sz }; } diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 0e26b014e04..e385a841507 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -37,7 +37,6 @@ set(DIST_HDRS ${PUBLIC_HDR_DIR}/${TARGET}/PrivateImplementation-impl.h ${PUBLIC_HDR_DIR}/${TARGET}/SingleInstanceComponentManager.h ${PUBLIC_HDR_DIR}/${TARGET}/Slice.h - ${PUBLIC_HDR_DIR}/${TARGET}/SpinLock.h ${PUBLIC_HDR_DIR}/${TARGET}/StructureOfArrays.h ${PUBLIC_HDR_DIR}/${TARGET}/unwindows.h ) @@ -148,6 +147,7 @@ set(TEST_SRCS test/test_CyclicBarrier.cpp test/test_Entity.cpp test/test_FixedCapacityVector.cpp + test/test_FixedCircularBuffer.cpp test/test_Hash.cpp test/test_JobSystem.cpp test/test_QuadTreeArray.cpp diff --git a/libs/utils/benchmark/benchmark_allocators.cpp b/libs/utils/benchmark/benchmark_allocators.cpp index b37e5c0232a..f243d7953c8 100644 --- a/libs/utils/benchmark/benchmark_allocators.cpp +++ b/libs/utils/benchmark/benchmark_allocators.cpp @@ -38,7 +38,6 @@ class Allocators : public benchmark::Fixture { utils::Arena, LockingPolicy::NoLock> mPoolAllocatorNoLock; utils::Arena, std::mutex> mPoolAllocatorStdMutex; utils::Arena, utils::Mutex> mPoolAllocatorUtilsMutex; - utils::Arena, LockingPolicy::SpinLock> mPoolAllocatorSpinlock; utils::Arena, LockingPolicy::NoLock> mPoolAllocatorAtomic; }; @@ -48,7 +47,6 @@ Allocators::Allocators() : mPoolAllocatorNoLock("nolock", POOL_ITEM_COUNT * sizeof(Payload)), mPoolAllocatorStdMutex("std::mutex", POOL_ITEM_COUNT * sizeof(Payload)), mPoolAllocatorUtilsMutex("utils::Mutex", POOL_ITEM_COUNT * sizeof(Payload)), - mPoolAllocatorSpinlock("spinlock", POOL_ITEM_COUNT * sizeof(Payload)), mPoolAllocatorAtomic("atomic", POOL_ITEM_COUNT * sizeof(Payload)) { } @@ -81,15 +79,6 @@ BENCHMARK_DEFINE_F(Allocators, poolAllocator_utils_mutex)(benchmark::State& stat } } -BENCHMARK_DEFINE_F(Allocators, poolAllocator_spinlock)(benchmark::State& state) { - auto& pool = mPoolAllocatorSpinlock; - PerformanceCounters pc(state); - for (auto _ : state) { - Payload* p = pool.alloc(1); - pool.free(p); - } -} - BENCHMARK_DEFINE_F(Allocators, poolAllocator_atomic)(benchmark::State& state) { auto& pool = mPoolAllocatorAtomic; PerformanceCounters pc(state); @@ -107,10 +96,6 @@ BENCHMARK_REGISTER_F(Allocators, poolAllocator_utils_mutex) ->ThreadRange(1, 4) ->Threads(benchmark::CPUInfo::Get().num_cpus * 2); -BENCHMARK_REGISTER_F(Allocators, poolAllocator_spinlock) - ->ThreadRange(1, 4) - ->Threads(benchmark::CPUInfo::Get().num_cpus * 2); - BENCHMARK_REGISTER_F(Allocators, poolAllocator_atomic) ->ThreadRange(1, 4) ->Threads(benchmark::CPUInfo::Get().num_cpus * 2); diff --git a/libs/utils/benchmark/benchmark_mutex.cpp b/libs/utils/benchmark/benchmark_mutex.cpp index b455d424cd0..71e4f21caf8 100644 --- a/libs/utils/benchmark/benchmark_mutex.cpp +++ b/libs/utils/benchmark/benchmark_mutex.cpp @@ -41,15 +41,6 @@ static void BM_utils_mutex(benchmark::State& state) { } } -static void BM_spinlock(benchmark::State& state) { - static LockingPolicy::SpinLock l; - PerformanceCounters pc(state); - for (auto _ : state) { - l.lock(); - l.unlock(); - } -} - BENCHMARK(BM_std_mutex) ->Threads(1) ->Threads(2) @@ -61,9 +52,3 @@ BENCHMARK(BM_utils_mutex) ->Threads(2) ->Threads(8) ->ThreadPerCpu(); - -BENCHMARK(BM_spinlock) - ->Threads(1) - ->Threads(2) - ->Threads(8) - ->ThreadPerCpu(); diff --git a/libs/utils/include/utils/Allocator.h b/libs/utils/include/utils/Allocator.h index 82d1d1ccee9..7b9978f5b90 100644 --- a/libs/utils/include/utils/Allocator.h +++ b/libs/utils/include/utils/Allocator.h @@ -17,12 +17,10 @@ #ifndef TNT_UTILS_ALLOCATOR_H #define TNT_UTILS_ALLOCATOR_H - #include #include #include #include -#include #include #include @@ -31,6 +29,8 @@ #include #include +#include +#include namespace utils { @@ -44,14 +44,14 @@ static inline P* add(P* a, T b) noexcept { template static inline P* align(P* p, size_t alignment) noexcept { // alignment must be a power-of-two - assert(alignment && !(alignment & alignment-1)); + assert_invariant(alignment && !(alignment & alignment-1)); return (P*)((uintptr_t(p) + alignment - 1) & ~(alignment - 1)); } template static inline P* align(P* p, size_t alignment, size_t offset) noexcept { P* const r = align(add(p, offset), alignment); - assert(r >= add(p, offset)); + assert_invariant(r >= add(p, offset)); return r; } @@ -90,20 +90,19 @@ class LinearAllocator { // branch-less allocation void* const p = pointermath::align(current(), alignment, extra); void* const c = pointermath::add(p, size); - bool success = c <= end(); + bool const success = c <= end(); set_current(success ? c : current()); return success ? p : nullptr; } // API specific to this allocator - void *getCurrent() UTILS_RESTRICT noexcept { return current(); } // free memory back to the specified point void rewind(void* p) UTILS_RESTRICT noexcept { - assert(p>=mBegin && p= mBegin && p < end()); set_current(p); } @@ -123,16 +122,21 @@ class LinearAllocator { void swap(LinearAllocator& rhs) noexcept; void *base() noexcept { return mBegin; } + void const *base() const noexcept { return mBegin; } void free(void*, size_t) UTILS_RESTRICT noexcept { } -private: +protected: void* end() UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mSize); } + void const* end() const UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mSize); } + void* current() UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mCur); } + void const* current() const UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mCur); } + +private: void set_current(void* p) UTILS_RESTRICT noexcept { mCur = uint32_t(uintptr_t(p) - uintptr_t(mBegin)); } - void* mBegin = nullptr; uint32_t mSize = 0; uint32_t mCur = 0; @@ -153,9 +157,7 @@ class HeapAllocator { explicit HeapAllocator(const AREA&) { } // our allocator concept - void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) { - // this allocator doesn't support 'extra' - assert(extra == 0); + void* alloc(size_t size, size_t alignment = alignof(std::max_align_t)) { return aligned_alloc(size, alignment); } @@ -172,6 +174,50 @@ class HeapAllocator { void swap(HeapAllocator&) noexcept { } }; +/* ------------------------------------------------------------------------------------------------ + * LinearAllocatorWithFallback + * + * This is a LinearAllocator that falls back to a HeapAllocator when allocation fail. The Heap + * allocator memory is freed only when the LinearAllocator is reset or destroyed. + * ------------------------------------------------------------------------------------------------ + */ +class LinearAllocatorWithFallback : private LinearAllocator, private HeapAllocator { + std::vector mHeapAllocations; +public: + LinearAllocatorWithFallback(void* begin, void* end) noexcept + : LinearAllocator(begin, end) { + } + + template + explicit LinearAllocatorWithFallback(const AREA& area) + : LinearAllocatorWithFallback(area.begin(), area.end()) { + } + + ~LinearAllocatorWithFallback() noexcept { + LinearAllocatorWithFallback::reset(); + } + + void* alloc(size_t size, size_t alignment = alignof(std::max_align_t)); + + void *getCurrent() noexcept { + return LinearAllocator::getCurrent(); + } + + void rewind(void* p) noexcept { + if (p >= LinearAllocator::base() && p < LinearAllocator::end()) { + LinearAllocator::rewind(p); + } + } + + void reset() noexcept; + + void free(void*, size_t) noexcept { } + + bool isHeapAllocation(void* p) const noexcept { + return p < LinearAllocator::base() || p >= LinearAllocator::end(); + } +}; + // ------------------------------------------------------------------------------------------------ class FreeList { @@ -187,13 +233,13 @@ class FreeList { Node* const head = mHead; mHead = head ? head->next : nullptr; // this could indicate a use after free - assert(!mHead || mHead >= mBegin && mHead < mEnd); + assert_invariant(!mHead || mHead >= mBegin && mHead < mEnd); return head; } void push(void* p) noexcept { - assert(p); - assert(p >= mBegin && p < mEnd); + assert_invariant(p); + assert_invariant(p >= mBegin && p < mEnd); // TODO: assert this is one of our pointer (i.e.: it's address match one of ours) Node* const head = static_cast(p); head->next = mHead; @@ -204,11 +250,11 @@ class FreeList { return mHead; } -private: struct Node { Node* next; }; +private: static Node* init(void* begin, void* end, size_t elementSize, size_t alignment, size_t extra) noexcept; @@ -226,20 +272,20 @@ class AtomicFreeList { AtomicFreeList() noexcept = default; AtomicFreeList(void* begin, void* end, size_t elementSize, size_t alignment, size_t extra) noexcept; - AtomicFreeList(const FreeList& rhs) = delete; - AtomicFreeList& operator=(const FreeList& rhs) = delete; + AtomicFreeList(const AtomicFreeList& rhs) = delete; + AtomicFreeList& operator=(const AtomicFreeList& rhs) = delete; void* pop() noexcept { - Node* const storage = mStorage; + Node* const pStorage = mStorage; HeadPtr currentHead = mHead.load(); while (currentHead.offset >= 0) { - // The value of "next" we load here might already contain application data if another + // The value of "pNext" we load here might already contain application data if another // thread raced ahead of us. But in that case, the computed "newHead" will be discarded // since compare_exchange_weak fails. Then this thread will loop with the updated // value of currentHead, and try again. - Node* const next = storage[currentHead.offset].next.load(std::memory_order_relaxed); - const HeadPtr newHead{ next ? int32_t(next - storage) : -1, currentHead.tag + 1 }; + Node* const pNext = pStorage[currentHead.offset].next.load(std::memory_order_relaxed); + const HeadPtr newHead{ pNext ? int32_t(pNext - pStorage) : -1, currentHead.tag + 1 }; // In the rare case that the other thread that raced ahead of us already returned the // same mHead we just loaded, but it now has a different "next" value, the tag field will not // match, and compare_exchange_weak will fail and prevent that particular race condition. @@ -247,18 +293,18 @@ class AtomicFreeList { // This assert needs to occur after we have validated that there was no race condition // Otherwise, next might already contain application data, if another thread // raced ahead of us after we loaded mHead, but before we loaded mHead->next. - assert(!next || next >= storage); + assert_invariant(!pNext || pNext >= pStorage); break; } } - void* p = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr; - assert(!p || p >= storage); + void* p = (currentHead.offset >= 0) ? (pStorage + currentHead.offset) : nullptr; + assert_invariant(!p || p >= pStorage); return p; } void push(void* p) noexcept { Node* const storage = mStorage; - assert(p && p >= storage); + assert_invariant(p && p >= storage); Node* const node = static_cast(p); HeadPtr currentHead = mHead.load(); HeadPtr newHead = { int32_t(node - storage), currentHead.tag + 1 }; @@ -273,7 +319,6 @@ class AtomicFreeList { return mStorage + mHead.load(std::memory_order_relaxed).offset; } -private: struct Node { // This should be a regular (non-atomic) pointer, but this causes TSAN to complain // about a data-race that exists but is benin. We always use this atomic<> in @@ -304,6 +349,7 @@ class AtomicFreeList { std::atomic next; }; +private: // This struct is using a 32-bit offset into the arena rather than // a direct pointer, because together with the 32-bit tag, it needs to // fit into 8 bytes. If it was any larger, it would not be possible to @@ -326,14 +372,15 @@ template < size_t OFFSET = 0, typename FREELIST = FreeList> class PoolAllocator { - static_assert(ELEMENT_SIZE >= sizeof(void*), "ELEMENT_SIZE must accommodate at least a pointer"); + static_assert(ELEMENT_SIZE >= sizeof(typename FREELIST::Node), + "ELEMENT_SIZE must accommodate at least a FreeList::Node"); public: // our allocator concept void* alloc(size_t size = ELEMENT_SIZE, size_t alignment = ALIGNMENT, size_t offset = OFFSET) noexcept { - assert(size <= ELEMENT_SIZE); - assert(alignment <= ALIGNMENT); - assert(offset == OFFSET); + assert_invariant(size <= ELEMENT_SIZE); + assert_invariant(alignment <= ALIGNMENT); + assert_invariant(offset == OFFSET); return mFreeList.pop(); } @@ -347,7 +394,11 @@ class PoolAllocator { : mFreeList(begin, end, ELEMENT_SIZE, ALIGNMENT, OFFSET) { } - template + PoolAllocator(void* begin, size_t size) noexcept + : PoolAllocator(begin, static_cast(begin) + size) { + } + + template explicit PoolAllocator(const AREA& area) noexcept : PoolAllocator(area.begin(), area.end()) { } @@ -373,6 +424,53 @@ class PoolAllocator { FREELIST mFreeList; }; +template < + size_t ELEMENT_SIZE, + size_t ALIGNMENT = alignof(std::max_align_t), + typename FREELIST = FreeList> +class PoolAllocatorWithFallback : + private PoolAllocator, + private HeapAllocator { + using PoolAllocator = PoolAllocator; + void* mBegin; + void* mEnd; +public: + PoolAllocatorWithFallback(void* begin, void* end) noexcept + : PoolAllocator(begin, end), mBegin(begin), mEnd(end) { + } + + PoolAllocatorWithFallback(void* begin, size_t size) noexcept + : PoolAllocatorWithFallback(begin, static_cast(begin) + size) { + } + + template + explicit PoolAllocatorWithFallback(const AREA& area) noexcept + : PoolAllocatorWithFallback(area.begin(), area.end()) { + } + + bool isHeapAllocation(void* p) const noexcept { + return p < mBegin || p >= mEnd; + } + + // our allocator concept + void* alloc(size_t size = ELEMENT_SIZE, size_t alignment = ALIGNMENT) noexcept { + void* p = PoolAllocator::alloc(size, alignment); + if (UTILS_UNLIKELY(!p)) { + p = HeapAllocator::alloc(size, alignment); + } + assert_invariant(p); + return p; + } + + void free(void* p, size_t size) noexcept { + if (UTILS_LIKELY(!isHeapAllocation(p))) { + PoolAllocator::free(p, size); + } else { + HeapAllocator::free(p); + } + } +}; + #define UTILS_MAX(a,b) ((a) > (b) ? (a) : (b)) template @@ -478,7 +576,6 @@ struct NoLock { void unlock() noexcept { } }; -using SpinLock = utils::SpinLock; using Mutex = utils::Mutex; } // namespace LockingPolicy @@ -587,32 +684,54 @@ class Arena { mListener(name, mArea.data(), mArea.size()) { } + template + void* alloc(size_t size, size_t alignment, size_t extra, ARGS&& ... args) noexcept { + std::lock_guard guard(mLock); + void* p = mAllocator.alloc(size, alignment, extra, std::forward(args) ...); + mListener.onAlloc(p, size, alignment, extra); + return p; + } + + // allocate memory from arena with given size and alignment // (acceptable size/alignment may depend on the allocator provided) - void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) noexcept { + void* alloc(size_t size, size_t alignment, size_t extra) noexcept { std::lock_guard guard(mLock); void* p = mAllocator.alloc(size, alignment, extra); mListener.onAlloc(p, size, alignment, extra); return p; } + void* alloc(size_t size, size_t alignment = alignof(std::max_align_t)) noexcept { + std::lock_guard guard(mLock); + void* p = mAllocator.alloc(size, alignment); + mListener.onAlloc(p, size, alignment, 0); + return p; + } + // Allocate an array of trivially destructible objects // for safety, we disable the object-based alloc method if the object type is not // trivially destructible, since free() won't call the destructor and this is allocating // an array. template ::value>::type> - T* alloc(size_t count, size_t alignment = alignof(T), size_t extra = 0) noexcept { + T* alloc(size_t count, size_t alignment, size_t extra) noexcept { return (T*)alloc(count * sizeof(T), alignment, extra); } - // return memory pointed by p to the arena - // (actual behaviour may depend on allocator provided) - void free(void* p) noexcept { + template ::value>::type> + T* alloc(size_t count, size_t alignment = alignof(T)) noexcept { + return (T*)alloc(count * sizeof(T), alignment); + } + + // some allocators require more parameters + template + void free(void* p, size_t size, ARGS&& ... args) noexcept { if (p) { std::lock_guard guard(mLock); - mListener.onFree(p); - mAllocator.free(p); + mListener.onFree(p, size); + mAllocator.free(p, size, std::forward(args) ...); } } @@ -625,6 +744,16 @@ class Arena { } } + // return memory pointed by p to the arena + // (actual behaviour may depend on allocator provided) + void free(void* p) noexcept { + if (p) { + std::lock_guard guard(mLock); + mListener.onFree(p); + mAllocator.free(p); + } + } + // some allocators don't have a free() call, but a single reset() or rewind() instead void reset() noexcept { std::lock_guard guard(mLock); @@ -722,6 +851,8 @@ class ArenaScope { } public: + using Arena = ARENA; + explicit ArenaScope(ARENA& allocator) : mArena(allocator), mRewind(allocator.getCurrent()) { } @@ -773,7 +904,7 @@ class ArenaScope { } // use with caution - ARENA& getAllocator() noexcept { return mArena; } + ARENA& getArena() noexcept { return mArena; } private: ARENA& mArena; @@ -800,7 +931,7 @@ class STLAllocator { public: // we don't make this explicit, so that we can initialize a vector using a STLAllocator - // from an Arena, avoiding to have to repeat the vector type. + // from an Arena, avoiding having to repeat the vector type. STLAllocator(ARENA& arena) : mArena(arena) { } // NOLINT(google-explicit-constructor) template diff --git a/libs/utils/include/utils/BitmaskEnum.h b/libs/utils/include/utils/BitmaskEnum.h index 56354956c5f..17f94d215be 100644 --- a/libs/utils/include/utils/BitmaskEnum.h +++ b/libs/utils/include/utils/BitmaskEnum.h @@ -17,13 +17,10 @@ #ifndef TNT_UTILS_BITMASKENUM_H #define TNT_UTILS_BITMASKENUM_H -#include - #include // for std::false_type #include #include -#include namespace utils { diff --git a/libs/utils/include/utils/CString.h b/libs/utils/include/utils/CString.h index 46a823b4c13..d5b7695106d 100644 --- a/libs/utils/include/utils/CString.h +++ b/libs/utils/include/utils/CString.h @@ -35,7 +35,7 @@ struct hashCStrings { typedef size_t result_type; result_type operator()(argument_type cstr) const noexcept { size_t hash = 5381; - while (int c = *cstr++) { + while (int const c = *cstr++) { hash = (hash * 33u) ^ size_t(c); } return hash; @@ -192,8 +192,8 @@ class UTILS_PUBLIC CString { }; int compare(const CString& rhs) const noexcept { - size_type lhs_size = size(); - size_type rhs_size = rhs.size(); + size_type const lhs_size = size(); + size_type const rhs_size = rhs.size(); if (lhs_size < rhs_size) return -1; if (lhs_size > rhs_size) return 1; return strncmp(data(), rhs.data(), size()); @@ -225,6 +225,28 @@ class UTILS_PUBLIC CString { template CString to_string(T value) noexcept; +// ------------------------------------------------------------------------------------------------ + +template +class UTILS_PUBLIC FixedSizeString { +public: + using value_type = char; + using pointer = value_type*; + using const_pointer = const value_type*; + static_assert(N > 0); + + FixedSizeString() noexcept = default; + explicit FixedSizeString(const char* str) noexcept { + strncpy(mData, str, N - 1); // leave room for the null terminator + } + + const_pointer c_str() const noexcept { return mData; } + pointer c_str() noexcept { return mData; } + +private: + value_type mData[N] = {0}; +}; + } // namespace utils #endif // TNT_UTILS_CSTRING_H diff --git a/libs/utils/include/utils/CallStack.h b/libs/utils/include/utils/CallStack.h index 291a748ce44..33ac0b504f4 100644 --- a/libs/utils/include/utils/CallStack.h +++ b/libs/utils/include/utils/CallStack.h @@ -22,7 +22,8 @@ #include #include -#include +#include +#include namespace utils { diff --git a/libs/utils/include/utils/CountDownLatch.h b/libs/utils/include/utils/CountDownLatch.h index 6367fffc7bc..62c3110f046 100644 --- a/libs/utils/include/utils/CountDownLatch.h +++ b/libs/utils/include/utils/CountDownLatch.h @@ -17,12 +17,13 @@ #ifndef TNT_UTILS_COUNTDOWNLATCH_H #define TNT_UTILS_COUNTDOWNLATCH_H -#include - // note: we use our version of mutex/condition to keep this public header STL free #include #include +#include +#include + namespace utils { /** diff --git a/libs/utils/include/utils/EntityInstance.h b/libs/utils/include/utils/EntityInstance.h index b164ed3fb2e..75419493fed 100644 --- a/libs/utils/include/utils/EntityInstance.h +++ b/libs/utils/include/utils/EntityInstance.h @@ -23,7 +23,6 @@ #include - namespace utils { class UTILS_PUBLIC EntityInstanceBase { @@ -77,7 +76,7 @@ class UTILS_PUBLIC EntityInstance : public EntityInstanceBase { // return a value for this Instance (mostly needed for debugging constexpr uint32_t asValue() const noexcept { return mInstance; } - // auto convert to Type so it can be used as an index + // auto convert to Type, so it can be used as an index constexpr operator Type() const noexcept { return mInstance; } // NOLINT(google-explicit-constructor) // conversion from Type so we can initialize from an index diff --git a/libs/utils/include/utils/EntityManager.h b/libs/utils/include/utils/EntityManager.h index 9674cac2554..5e2eaa1b00c 100644 --- a/libs/utils/include/utils/EntityManager.h +++ b/libs/utils/include/utils/EntityManager.h @@ -17,12 +17,13 @@ #ifndef TNT_UTILS_ENTITYMANAGER_H #define TNT_UTILS_ENTITYMANAGER_H -#include -#include - #include #include +#include +#include +#include + #ifndef FILAMENT_UTILS_TRACK_ENTITIES #define FILAMENT_UTILS_TRACK_ENTITIES false #endif @@ -44,23 +45,25 @@ class UTILS_PUBLIC EntityManager { public: virtual void onEntitiesDestroyed(size_t n, Entity const* entities) noexcept = 0; protected: - ~Listener() noexcept; + virtual ~Listener() noexcept; }; - // maximum number of entities that can exist at the same time static size_t getMaxEntityCount() noexcept { // because index 0 is reserved, we only have 2^GENERATION_SHIFT - 1 valid indices return RAW_INDEX_COUNT - 1; } - // create n entities. Thread safe. + // number of active Entities + size_t getEntityCount() const noexcept; + + // Create n entities. Thread safe. void create(size_t n, Entity* entities); // destroys n entities. Thread safe. void destroy(size_t n, Entity* entities) noexcept; - // create a new Entity. Thread safe. + // Create a new Entity. Thread safe. // Return Entity.isNull() if the entity cannot be allocated. Entity create() { Entity e; @@ -68,20 +71,20 @@ class UTILS_PUBLIC EntityManager { return e; } - // destroys an Entity. Thread safe. + // Destroys an Entity. Thread safe. void destroy(Entity e) noexcept { destroy(1, &e); } - // return whether the given Entity has been destroyed (false) or not (true). + // Return whether the given Entity has been destroyed (false) or not (true). // Thread safe. bool isAlive(Entity e) const noexcept { assert(getIndex(e) < RAW_INDEX_COUNT); return (!e.isNull()) && (getGeneration(e) == mGens[getIndex(e)]); } - // registers a listener to be called when an entity is destroyed. thread safe. - // if the listener is already register, this method has no effect. + // Registers a listener to be called when an entity is destroyed. Thread safe. + // If the listener is already registered, this method has no effect. void registerListener(Listener* l) noexcept; // unregisters a listener. @@ -94,6 +97,7 @@ class UTILS_PUBLIC EntityManager { uint8_t getGenerationForIndex(size_t index) const noexcept { return mGens[index]; } + // singleton, can't be copied EntityManager(const EntityManager& rhs) = delete; EntityManager& operator=(const EntityManager& rhs) = delete; diff --git a/libs/utils/include/utils/FixedCapacityVector.h b/libs/utils/include/utils/FixedCapacityVector.h index 540b42b21d7..f3990124854 100644 --- a/libs/utils/include/utils/FixedCapacityVector.h +++ b/libs/utils/include/utils/FixedCapacityVector.h @@ -17,16 +17,18 @@ #ifndef TNT_UTILS_FIXEDCAPACITYVECTOR_H #define TNT_UTILS_FIXEDCAPACITYVECTOR_H +#include #include #include #include +#include #include #include #include #include -#include // TODO: is this necessary? +#include #include #include diff --git a/libs/utils/include/utils/FixedCircularBuffer.h b/libs/utils/include/utils/FixedCircularBuffer.h new file mode 100644 index 00000000000..dd3cb75a327 --- /dev/null +++ b/libs/utils/include/utils/FixedCircularBuffer.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef TNT_UTILS_FIXEDCIRCULARBUFFER_H +#define TNT_UTILS_FIXEDCIRCULARBUFFER_H + +#include + +#include +#include +#include + +#include + +namespace utils { + +template +class FixedCircularBuffer { +public: + explicit FixedCircularBuffer(size_t capacity) + : mData(std::make_unique(capacity)), mCapacity(capacity) {} + + size_t size() const noexcept { return mSize; } + size_t capacity() const noexcept { return mCapacity; } + bool full() const noexcept { return mCapacity > 0 && mSize == mCapacity; } + bool empty() const noexcept { return mSize == 0; } + + /** + * Push v into the buffer. If the buffer is already full, removes the oldest item and returns + * it. If this buffer has no capacity, simply returns v. + * @param v the new value to push into the buffer + * @return if the buffer was full, the oldest value which was displaced + */ + std::optional push(T v) noexcept { + if (mCapacity == 0) { + return v; + } + std::optional displaced = full() ? pop() : std::optional{}; + mData[mEnd] = v; + mEnd = (mEnd + 1) % mCapacity; + mSize++; + return displaced; + } + + T pop() noexcept { + assert_invariant(mSize > 0); + T result = mData[mBegin]; + mBegin = (mBegin + 1) % mCapacity; + mSize--; + return result; + } + +private: + std::unique_ptr mData; + + size_t mBegin = 0; + size_t mEnd = 0; + size_t mSize = 0; + size_t mCapacity; +}; + +} // namespace utils + +#endif // TNT_UTILS_FIXEDCIRCULARBUFFER_H diff --git a/libs/utils/include/utils/Hash.h b/libs/utils/include/utils/Hash.h index cc61a4067bb..c269bb7e83e 100644 --- a/libs/utils/include/utils/Hash.h +++ b/libs/utils/include/utils/Hash.h @@ -18,10 +18,11 @@ #define TNT_UTILS_HASH_H #include // for std::hash +#include +#include #include #include -#include namespace utils::hash { @@ -59,7 +60,7 @@ inline uint32_t murmurSlow(const uint8_t* key, size_t byteCount, uint32_t seed) // The remainder is identical to murmur3() except an inner loop safely "reads" an entire word. uint32_t h = seed; - size_t i = wordCount; + size_t wc = wordCount; do { uint32_t k = 0; for (int i = 0; i < 4 && key < last; ++i, ++key) { @@ -72,7 +73,7 @@ inline uint32_t murmurSlow(const uint8_t* key, size_t byteCount, uint32_t seed) h ^= k; h = (h << 13u) | (h >> 19u); h = (h * 5u) + 0xe6546b64u; - } while (--i); + } while (--wc); h ^= wordCount; h ^= h >> 16u; h *= 0x85ebca6bu; diff --git a/libs/utils/include/utils/JobSystem.h b/libs/utils/include/utils/JobSystem.h index 1c602740de1..670a74a8fe5 100644 --- a/libs/utils/include/utils/JobSystem.h +++ b/libs/utils/include/utils/JobSystem.h @@ -17,15 +17,6 @@ #ifndef TNT_UTILS_JOBSYSTEM_H #define TNT_UTILS_JOBSYSTEM_H -#include - -#include -#include -#include -#include - -#include - #include #include #include @@ -34,8 +25,22 @@ #include #include #include +#include #include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + namespace utils { class JobSystem { @@ -169,8 +174,9 @@ class JobSystem { // the caller must ensure the object will outlive the Job template Job* createJob(Job* parent, T* data) noexcept { - Job* job = create(parent, [](void* user, JobSystem& js, Job* job) { - (*static_cast(user)->*method)(js, job); + Job* job = create(parent, +[](void* storage, JobSystem& js, Job* job) { + T* const that = static_cast(reinterpret_cast(storage)[0]); + (that->*method)(js, job); }); if (job) { job->storage[0] = data; @@ -182,8 +188,8 @@ class JobSystem { template Job* createJob(Job* parent, T data) noexcept { static_assert(sizeof(data) <= sizeof(Job::storage), "user data too large"); - Job* job = create(parent, [](void* user, JobSystem& js, Job* job) { - T* that = static_cast(user); + Job* job = create(parent, [](void* storage, JobSystem& js, Job* job) { + T* const that = static_cast(storage); (that->*method)(js, job); that->~T(); }); @@ -197,10 +203,10 @@ class JobSystem { template Job* createJob(Job* parent, T functor) noexcept { static_assert(sizeof(functor) <= sizeof(Job::storage), "functor too large"); - Job* job = create(parent, [](void* user, JobSystem& js, Job* job){ - T& that = *static_cast(user); - that(js, job); - that.~T(); + Job* job = create(parent, [](void* storage, JobSystem& js, Job* job){ + T* const that = static_cast(storage); + that->operator()(js, job); + that->~T(); }); if (job) { new(job->storage) T(std::move(functor)); @@ -252,7 +258,7 @@ class JobSystem { void signal() noexcept; /* - * Add job to this thread's execution queue and and keep a reference to it. + * Add job to this thread's execution queue and keep a reference to it. * Current thread must be owned by JobSystem's thread pool. See adopt(). * * This job MUST BE waited on with wait(), or released with release(). @@ -386,7 +392,7 @@ class JobSystem { uint8_t mParallelSplitCount = 0; // # of split allowable in parallel_for Job* mRootJob = nullptr; - utils::SpinLock mThreadMapLock; // this should have very little contention + utils::Mutex mThreadMapLock; // this should have very little contention tsl::robin_map mThreadMap; }; diff --git a/libs/utils/include/utils/NameComponentManager.h b/libs/utils/include/utils/NameComponentManager.h index 9e31e4618de..4ac7435a2bb 100644 --- a/libs/utils/include/utils/NameComponentManager.h +++ b/libs/utils/include/utils/NameComponentManager.h @@ -24,7 +24,6 @@ #include #include -#include namespace utils { @@ -48,7 +47,7 @@ class EntityManager; * printf("%s\n", names->getName(names->getInstance(myEntity)); * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -class UTILS_PUBLIC NameComponentManager : public SingleInstanceComponentManager { +class UTILS_PUBLIC NameComponentManager : private SingleInstanceComponentManager { public: using Instance = EntityInstance; @@ -75,15 +74,6 @@ class UTILS_PUBLIC NameComponentManager : public SingleInstanceComponentManager< return { SingleInstanceComponentManager::getInstance(e) }; } - /*! \cond PRIVATE */ - // these are implemented in SingleInstanceComponentManager<>, but we need to - // reimplement them in each manager, to ensure they are generated in an implementation file - // for backward binary compatibility reasons. - size_t getComponentCount() const noexcept; - Entity const* getEntities() const noexcept; - void gc(const EntityManager& em, size_t ratio = 4) noexcept; - /*! \endcond */ - /** * Adds a name component to the given entity if it doesn't already exist. */ @@ -105,6 +95,12 @@ class UTILS_PUBLIC NameComponentManager : public SingleInstanceComponentManager< * @return pointer to the copy that was made during setName() */ const char* getName(Instance instance) const noexcept; + + void gc(EntityManager& em) noexcept { + SingleInstanceComponentManager::gc(em, [this](Entity e) { + removeComponent(e); + }); + } }; } // namespace utils diff --git a/libs/utils/include/utils/Panic.h b/libs/utils/include/utils/Panic.h index df24cac2388..c658da4b14f 100644 --- a/libs/utils/include/utils/Panic.h +++ b/libs/utils/include/utils/Panic.h @@ -17,11 +17,11 @@ #ifndef TNT_UTILS_PANIC_H #define TNT_UTILS_PANIC_H -#include - #include #include +#include + #ifdef __EXCEPTIONS # define UTILS_EXCEPTIONS 1 #else @@ -250,42 +250,71 @@ namespace utils { */ class UTILS_PUBLIC Panic { public: + + using PanicHandlerCallback = void(*)(void* user, Panic const& panic); + + /** + * Sets a user-defined handler for the Panic. If exceptions are enabled, the concrete Panic + * object will be thrown upon return; moreover it is acceptable to throw from the provided + * callback, but it is unsafe to throw the Panic object itself, since it's just an interface. + * It is also acceptable to abort from the callback. If exceptions are not enabled, std::abort() + * will be automatically called upon return. + * + * The PanicHandlerCallback can be called from any thread. + * + * Caveat: this API can misbehave if is used as a static library in multiple translation units, + * some of these translation units might not see the callback. + * + * @param handler pointer to the user defined handler for the Panic + * @param user user pointer given back to the callback + */ + static void setPanicHandler(PanicHandlerCallback handler, void* user) noexcept; + + virtual ~Panic() noexcept; /** - * @return a detailed description of the error + * @return a formatted and detailed description of the error including all available + * information. * @see std::exception */ virtual const char* what() const noexcept = 0; /** - * Get the function name where the panic was detected + * Get the reason string for the panic + * @return a C string containing the reason for the panic + */ + virtual const char* getReason() const noexcept = 0; + + /** + * Get the function name where the panic was detected. On debug build the fully qualified + * function name is returned; on release builds only the function name is. * @return a C string containing the function name where the panic was detected */ virtual const char* getFunction() const noexcept = 0; /** - * Get the file name where the panic was detected + * Get the file name where the panic was detected. Only available on debug builds. * @return a C string containing the file name where the panic was detected */ virtual const char* getFile() const noexcept = 0; /** - * Get the line number in the file where the panic was detected + * Get the line number in the file where the panic was detected. Only available on debug builds. * @return an integer containing the line number in the file where the panic was detected */ virtual int getLine() const noexcept = 0; /** - * Logs this exception to the system-log + * Get the CallStack when the panic was detected if available. + * @return the CallStack when the panic was detected */ - virtual void log() const noexcept = 0; + virtual const CallStack& getCallStack() const noexcept = 0; /** - * Get the CallStack when the panic was detected - * @return the CallStack when the panic was detected + * Logs this exception to the system-log */ - virtual const CallStack& getCallStack() const noexcept = 0; + virtual void log() const noexcept = 0; }; // ----------------------------------------------------------------------------------------------- @@ -305,6 +334,7 @@ class UTILS_PUBLIC TPanic : public Panic { const char* what() const noexcept override; // Panic interface + const char* getReason() const noexcept override; const char* getFunction() const noexcept override; const char* getFile() const noexcept override; int getLine() const noexcept override; diff --git a/libs/utils/include/utils/PrivateImplementation.h b/libs/utils/include/utils/PrivateImplementation.h index 7cb510fae43..ee0ac48aa76 100644 --- a/libs/utils/include/utils/PrivateImplementation.h +++ b/libs/utils/include/utils/PrivateImplementation.h @@ -17,8 +17,6 @@ #ifndef UTILS_PRIVATEIMPLEMENTATION_H #define UTILS_PRIVATEIMPLEMENTATION_H -#include - #include namespace utils { diff --git a/libs/utils/include/utils/Profiler.h b/libs/utils/include/utils/Profiler.h index f41bd1e9a91..3de9a9b0589 100644 --- a/libs/utils/include/utils/Profiler.h +++ b/libs/utils/include/utils/Profiler.h @@ -17,12 +17,14 @@ #ifndef TNT_UTILS_PROFILER_H #define TNT_UTILS_PROFILER_H +#include +#include // note: This is safe (only used inline) + #include +#include #include #include -#include // note: This is safe (only used inline) - #if defined(__linux__) # include # include @@ -82,6 +84,7 @@ class Profiler { class Counters { friend class Profiler; + uint64_t nr; uint64_t time_enabled; uint64_t time_running; diff --git a/libs/utils/include/utils/QuadTree.h b/libs/utils/include/utils/QuadTree.h index 14302d34e7a..3578b5999c5 100644 --- a/libs/utils/include/utils/QuadTree.h +++ b/libs/utils/include/utils/QuadTree.h @@ -17,7 +17,6 @@ #ifndef TNT_UTILS_QUADTREE_H #define TNT_UTILS_QUADTREE_H -#include #include #include diff --git a/libs/utils/include/utils/Range.h b/libs/utils/include/utils/Range.h index b8499d78ad8..328ff980269 100644 --- a/libs/utils/include/utils/Range.h +++ b/libs/utils/include/utils/Range.h @@ -17,10 +17,10 @@ #ifndef TNT_UTILS_RANGE_H #define TNT_UTILS_RANGE_H -#include - #include +#include + namespace utils { template diff --git a/libs/utils/include/utils/RangeMap.h b/libs/utils/include/utils/RangeMap.h index 32b59f255a6..3d69bec8abb 100644 --- a/libs/utils/include/utils/RangeMap.h +++ b/libs/utils/include/utils/RangeMap.h @@ -22,6 +22,9 @@ #include #include +#include + +#include namespace utils { diff --git a/libs/utils/include/utils/SingleInstanceComponentManager.h b/libs/utils/include/utils/SingleInstanceComponentManager.h index c03ec5f100c..ddd538f5e9f 100644 --- a/libs/utils/include/utils/SingleInstanceComponentManager.h +++ b/libs/utils/include/utils/SingleInstanceComponentManager.h @@ -98,7 +98,7 @@ class UTILS_PUBLIC SingleInstanceComponentManager { return pos != map.end() ? pos->second : 0; } - // returns the number of components (i.e. size of each arrays) + // Returns the number of components (i.e. size of each array) size_t getComponentCount() const noexcept { // The array as an extra dummy component at index 0, so the visible count is 1 less. return mData.size() - 1; @@ -108,11 +108,8 @@ class UTILS_PUBLIC SingleInstanceComponentManager { return getComponentCount() == 0; } - // returns a pointer to the Entity array. This is basically the list - // of entities this component manager handles. - // The pointer becomes invalid when adding or removing a component. - Entity const* getEntities() const noexcept { - return begin(); + utils::Entity const* getEntities() const noexcept { + return data() + 1; } Entity getEntity(Instance i) const noexcept { @@ -128,14 +125,6 @@ class UTILS_PUBLIC SingleInstanceComponentManager { // This invalidates all pointers components. inline Instance removeComponent(Entity e); - // trigger one round of garbage collection. this is intended to be called on a regular - // basis. This gc gives up after it cannot randomly free 'ratio' component in a row. - void gc(const EntityManager& em, size_t ratio = 4) noexcept { - gc(em, ratio, [this](Entity e) { - removeComponent(e); - }); - } - // return the first instance Instance begin() const noexcept { return 1u; } @@ -234,24 +223,33 @@ class UTILS_PUBLIC SingleInstanceComponentManager { } } + template + void gc(const EntityManager& em, + REMOVE&& removeComponent) noexcept { + gc(em, 4, std::forward(removeComponent)); + } + template void gc(const EntityManager& em, size_t ratio, - REMOVE removeComponent) noexcept { - Entity const* entities = getEntities(); + REMOVE&& removeComponent) noexcept { + Entity const* const pEntities = begin(); size_t count = getComponentCount(); size_t aliveInARow = 0; default_random_engine& rng = mRng; UTILS_NOUNROLL while (count && aliveInARow < ratio) { + assert_invariant(count == getComponentCount()); // note: using the modulo favorizes lower number - size_t i = rng() % count; - if (UTILS_LIKELY(em.isAlive(entities[i]))) { + size_t const i = rng() % count; + Entity const entity = pEntities[i]; + assert_invariant(entity); + if (UTILS_LIKELY(em.isAlive(entity))) { ++aliveInARow; continue; } + removeComponent(entity); aliveInARow = 0; count--; - removeComponent(entities[i]); } } diff --git a/libs/utils/include/utils/SpinLock.h b/libs/utils/include/utils/SpinLock.h deleted file mode 100644 index e7ce00afac5..00000000000 --- a/libs/utils/include/utils/SpinLock.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TNT_UTILS_SPINLOCK_H -#define TNT_UTILS_SPINLOCK_H - -#include - -#include - -#include -#include - -#include -#include - -namespace utils { -namespace details { - -class SpinLock { - std::atomic_flag mLock = ATOMIC_FLAG_INIT; - -public: - void lock() noexcept { - UTILS_PREFETCHW(&mLock); -#ifdef __ARM_ACLE - // we signal an event on this CPU, so that the first yield() will be a no-op, - // and falls through the test_and_set(). This is more efficient than a while { } - // construct. - UTILS_SIGNAL_EVENT(); - do { - yield(); - } while (mLock.test_and_set(std::memory_order_acquire)); -#else - goto start; - do { - yield(); -start: ; - } while (mLock.test_and_set(std::memory_order_acquire)); -#endif - } - - void unlock() noexcept { - mLock.clear(std::memory_order_release); -#ifdef __ARM_ARCH_7A__ - // on ARMv7a SEL is needed - UTILS_SIGNAL_EVENT(); - // as well as a memory barrier is needed - __dsb(0xA); // ISHST = 0xA (b1010) -#else - // on ARMv8 we could avoid the call to SE, but we'd need to write the - // test_and_set() above by hand, so the WFE only happens without a STRX first. - UTILS_BROADCAST_EVENT(); -#endif - } - -private: - inline void yield() noexcept { - // on x86 call pause instruction, on ARM call WFE - UTILS_WAIT_FOR_EVENT(); - } -}; -} // namespace details - -#if UTILS_HAS_SANITIZE_THREAD -// Active spins with atomics slow down execution too much under ThreadSanitizer. -using SpinLock = Mutex; -#elif defined(__ARM_ARCH_7A__) -// We've had problems with "wfe" on some ARM-V7 devices, causing spurious SIGILL -using SpinLock = Mutex; -#else -using SpinLock = details::SpinLock; -#endif - -} // namespace utils - -#endif // TNT_UTILS_SPINLOCK_H diff --git a/libs/utils/include/utils/Stopwatch.h b/libs/utils/include/utils/Stopwatch.h index fcd3051232c..37f54d0a086 100644 --- a/libs/utils/include/utils/Stopwatch.h +++ b/libs/utils/include/utils/Stopwatch.h @@ -18,11 +18,13 @@ #define TNT_UTILS_STOPWATCH_H #include +#include #include #include +#include -#include +#include namespace utils { diff --git a/libs/utils/include/utils/StructureOfArrays.h b/libs/utils/include/utils/StructureOfArrays.h index 497a0fefbd2..a430958470c 100644 --- a/libs/utils/include/utils/StructureOfArrays.h +++ b/libs/utils/include/utils/StructureOfArrays.h @@ -17,8 +17,10 @@ #ifndef TNT_UTILS_STRUCTUREOFARRAYS_H #define TNT_UTILS_STRUCTUREOFARRAYS_H +#include #include #include +#include #include #include @@ -555,7 +557,7 @@ class StructureOfArraysBase { } inline void resizeNoCheck(size_t needed) noexcept { - assert(mCapacity >= needed); + assert_invariant(mCapacity >= needed); if (needed < mSize) { // we shrink the arrays destroy_each(needed, mSize); diff --git a/libs/utils/include/utils/WorkStealingDequeue.h b/libs/utils/include/utils/WorkStealingDequeue.h index 73b1ce6ec79..9e737d0aa77 100644 --- a/libs/utils/include/utils/WorkStealingDequeue.h +++ b/libs/utils/include/utils/WorkStealingDequeue.h @@ -21,6 +21,7 @@ #include #include +#include namespace utils { diff --git a/libs/utils/include/utils/Zip2Iterator.h b/libs/utils/include/utils/Zip2Iterator.h index 7b9f552a5c1..99eea52ecce 100644 --- a/libs/utils/include/utils/Zip2Iterator.h +++ b/libs/utils/include/utils/Zip2Iterator.h @@ -19,8 +19,9 @@ #include #include +#include -#include +#include namespace utils { diff --git a/libs/utils/include/utils/android/ThermalManager.h b/libs/utils/include/utils/android/ThermalManager.h index 6c303b04bba..fb951b3125a 100644 --- a/libs/utils/include/utils/android/ThermalManager.h +++ b/libs/utils/include/utils/android/ThermalManager.h @@ -17,8 +17,6 @@ #ifndef TNT_UTILS_ANDROID_THERMALMANAGER_H #define TNT_UTILS_ANDROID_THERMALMANAGER_H -#include - #include struct AThermalManager; diff --git a/libs/utils/include/utils/compiler.h b/libs/utils/include/utils/compiler.h index 7d62a389bb8..710c901eef8 100644 --- a/libs/utils/include/utils/compiler.h +++ b/libs/utils/include/utils/compiler.h @@ -179,6 +179,14 @@ # define UTILS_HAS_FEATURE_CXX_THREAD_LOCAL 0 #endif +#if defined(__clang__) +#define UTILS_NONNULL _Nonnull +#define UTILS_NULLABLE _Nullable +#else +#define UTILS_NONNULL +#define UTILS_NULLABLE +#endif + #if defined(_MSC_VER) // MSVC does not support loop unrolling hints # define UTILS_UNROLL diff --git a/libs/utils/include/utils/debug.h b/libs/utils/include/utils/debug.h index 4c118725177..0f6ecdb27e1 100644 --- a/libs/utils/include/utils/debug.h +++ b/libs/utils/include/utils/debug.h @@ -20,6 +20,7 @@ #include namespace utils { +UTILS_PUBLIC void panic(const char *func, const char * file, int line, const char *assertion) noexcept; } // namespace filament diff --git a/libs/utils/include/utils/generic/Condition.h b/libs/utils/include/utils/generic/Condition.h index accde2be9fa..89e05251e37 100644 --- a/libs/utils/include/utils/generic/Condition.h +++ b/libs/utils/include/utils/generic/Condition.h @@ -19,6 +19,8 @@ #include +#include + namespace utils { class Condition : public std::condition_variable { diff --git a/libs/utils/include/utils/generic/ThermalManager.h b/libs/utils/include/utils/generic/ThermalManager.h index 2d0088e5617..5d77ab5b611 100644 --- a/libs/utils/include/utils/generic/ThermalManager.h +++ b/libs/utils/include/utils/generic/ThermalManager.h @@ -17,8 +17,6 @@ #ifndef TNT_UTILS_GENERIC_THERMALMANAGER_H #define TNT_UTILS_GENERIC_THERMALMANAGER_H -#include - #include namespace utils { diff --git a/libs/utils/include/utils/linux/Mutex.h b/libs/utils/include/utils/linux/Mutex.h index 2dcc7ebc61c..f548d53effa 100644 --- a/libs/utils/include/utils/linux/Mutex.h +++ b/libs/utils/include/utils/linux/Mutex.h @@ -17,9 +17,11 @@ #ifndef TNT_UTILS_LINUX_MUTEX_H #define TNT_UTILS_LINUX_MUTEX_H +#include + #include -#include +#include namespace utils { diff --git a/libs/utils/include/utils/ostream.h b/libs/utils/include/utils/ostream.h index cc4df031fae..cde8e75bd27 100644 --- a/libs/utils/include/utils/ostream.h +++ b/libs/utils/include/utils/ostream.h @@ -29,7 +29,7 @@ namespace utils::io { struct ostream_; -class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { +class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { friend struct ostream_; public: @@ -69,6 +69,13 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { ostream& dec() noexcept; ostream& hex() noexcept; + /*! @cond PRIVATE */ + // Sets a consumer of the log. The consumer is invoked on flush() and replaces the default. + // Thread safe and reentrant. + using ConsumerCallback = void(*)(void*, char const*); + void setConsumer(ConsumerCallback consumer, void* user) noexcept; + /*! @endcond */ + protected: ostream& print(const char* format, ...) noexcept; @@ -85,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; @@ -104,7 +112,7 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { friend ostream& hex(ostream& s) noexcept; friend ostream& dec(ostream& s) noexcept; friend ostream& endl(ostream& s) noexcept; - friend ostream& flush(ostream& s) noexcept; + UTILS_PUBLIC friend ostream& flush(ostream& s) noexcept; enum type { SHORT, USHORT, CHAR, UCHAR, INT, UINT, LONG, ULONG, LONG_LONG, ULONG_LONG, FLOAT, DOUBLE, @@ -132,8 +140,7 @@ inline ostream& operator<<(ostream& stream, const VECTOR& v) { inline ostream& hex(ostream& s) noexcept { return s.hex(); } inline ostream& dec(ostream& s) noexcept { return s.dec(); } -inline ostream& endl(ostream& s) noexcept { s << '\n'; return s.flush(); } -inline ostream& flush(ostream& s) noexcept { return s.flush(); } +inline ostream& endl(ostream& s) noexcept { return flush(s << '\n'); } } // namespace utils::io 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/include/utils/string.h b/libs/utils/include/utils/string.h index 040044c05e6..7823260b919 100644 --- a/libs/utils/include/utils/string.h +++ b/libs/utils/include/utils/string.h @@ -17,8 +17,6 @@ #ifndef TNT_UTILS_STRING_H #define TNT_UTILS_STRING_H -#include - namespace utils { float strtof_c(const char* start, char** end); diff --git a/libs/utils/src/Allocator.cpp b/libs/utils/src/Allocator.cpp index 2d7a8fcbe92..fd6e5945691 100644 --- a/libs/utils/src/Allocator.cpp +++ b/libs/utils/src/Allocator.cpp @@ -16,6 +16,8 @@ #include +#include +#include #include #include @@ -52,6 +54,29 @@ void LinearAllocator::swap(LinearAllocator& rhs) noexcept { std::swap(mCur, rhs.mCur); } + +// ------------------------------------------------------------------------------------------------ +// LinearAllocatorWithFallback +// ------------------------------------------------------------------------------------------------ + +void* LinearAllocatorWithFallback::alloc(size_t size, size_t alignment) { + void* p = LinearAllocator::alloc(size, alignment); + if (UTILS_UNLIKELY(!p)) { + p = HeapAllocator::alloc(size, alignment); + mHeapAllocations.push_back(p); + } + assert_invariant(p); + return p; +} + +void LinearAllocatorWithFallback::reset() noexcept { + LinearAllocator::reset(); + for (auto* p : mHeapAllocations) { + HeapAllocator::free(p); + } + mHeapAllocations.clear(); +} + // ------------------------------------------------------------------------------------------------ // FreeList // ------------------------------------------------------------------------------------------------ @@ -61,8 +86,8 @@ FreeList::Node* FreeList::init(void* begin, void* end, { void* const p = pointermath::align(begin, alignment, extra); void* const n = pointermath::align(pointermath::add(p, elementSize), alignment, extra); - assert(p >= begin && p < end); - assert(n >= begin && n < end && n > p); + assert_invariant(p >= begin && p < end); + assert_invariant(n >= begin && n < end && n > p); const size_t d = uintptr_t(n) - uintptr_t(p); const size_t num = (uintptr_t(end) - uintptr_t(p)) / d; @@ -77,8 +102,8 @@ FreeList::Node* FreeList::init(void* begin, void* end, cur->next = next; cur = next; } - assert(cur < end); - assert(pointermath::add(cur, d) <= end); + assert_invariant(cur < end); + assert_invariant(pointermath::add(cur, d) <= end); cur->next = nullptr; return head; } @@ -97,13 +122,13 @@ AtomicFreeList::AtomicFreeList(void* begin, void* end, { #ifdef __ANDROID__ // on some platform (e.g. web) this returns false. we really only care about mobile though. - assert(mHead.is_lock_free()); + assert_invariant(mHead.is_lock_free()); #endif void* const p = pointermath::align(begin, alignment, extra); void* const n = pointermath::align(pointermath::add(p, elementSize), alignment, extra); - assert(p >= begin && p < end); - assert(n >= begin && n < end && n > p); + assert_invariant(p >= begin && p < end); + assert_invariant(n >= begin && n < end && n > p); const size_t d = uintptr_t(n) - uintptr_t(p); const size_t num = (uintptr_t(end) - uintptr_t(p)) / d; @@ -119,8 +144,8 @@ AtomicFreeList::AtomicFreeList(void* begin, void* end, cur->next = next; cur = next; } - assert(cur < end); - assert(pointermath::add(cur, d) <= end); + assert_invariant(cur < end); + assert_invariant(pointermath::add(cur, d) <= end); cur->next = nullptr; mHead.store({ int32_t(head - mStorage), 0 }); @@ -148,22 +173,25 @@ TrackingPolicy::HighWatermark::~HighWatermark() noexcept { } void TrackingPolicy::HighWatermark::onFree(void* p, size_t size) noexcept { - assert(mCurrent >= size); + // FIXME: this code is incorrect with LinearAllocators because free() is a no-op for them + assert_invariant(mCurrent >= size); mCurrent -= uint32_t(size); } void TrackingPolicy::HighWatermark::onReset() noexcept { // we should never be here if mBase is nullptr because compilation would have failed when // Arena::onReset() tries to call the underlying allocator's onReset() - assert(mBase); + assert_invariant(mBase); mCurrent = 0; } void TrackingPolicy::HighWatermark::onRewind(void const* addr) noexcept { // we should never be here if mBase is nullptr because compilation would have failed when // Arena::onRewind() tries to call the underlying allocator's onReset() - assert(mBase); - assert(addr >= mBase); - mCurrent = uint32_t(uintptr_t(addr) - uintptr_t(mBase)); + assert_invariant(mBase); + // for LinearAllocatorWithFallback we could get pointers outside the range + if (addr >= mBase && addr < pointermath::add(mBase, mSize)) { + mCurrent = uint32_t(uintptr_t(addr) - uintptr_t(mBase)); + } } // ------------------------------------------------------------------------------------------------ @@ -183,7 +211,7 @@ void TrackingPolicy::Debug::onFree(void* p, size_t size) noexcept { void TrackingPolicy::Debug::onReset() noexcept { // we should never be here if mBase is nullptr because compilation would have failed when // Arena::onReset() tries to call the underlying allocator's onReset() - assert(mBase); + assert_invariant(mBase); memset(mBase, 0xec, mSize); } diff --git a/libs/utils/src/CallStack.cpp b/libs/utils/src/CallStack.cpp index b41c18776da..38549d59d09 100644 --- a/libs/utils/src/CallStack.cpp +++ b/libs/utils/src/CallStack.cpp @@ -15,14 +15,19 @@ */ #include -#include +#include +#include +#include -#include -#include #include #include +#include + +#include +#include +#include -// FIXME: Some platforms do not have execinfo.h (but have unwind.h) +// FIXME: Some platforms do not have execinfo.h #if !defined(__ANDROID__) && !defined(WIN32) && !defined(__EMSCRIPTEN__) #include #define HAS_EXECINFO 1 @@ -30,6 +35,13 @@ #define HAS_EXECINFO 0 #endif +// execinfo.h is available as of Android 33 +#if defined(__ANDROID__) && (__ANDROID_API__ >= 33) +#include +#undef HAS_EXECINFO +#define HAS_EXECINFO 1 +#endif + #if !defined(WIN32) && !defined(__EMSCRIPTEN__) #include #define HAS_DLADDR 1 @@ -81,19 +93,19 @@ void CallStack::update(size_t ignore) noexcept { update_gcc(ignore); } -void CallStack::update_gcc(size_t ignore) noexcept { +void CallStack::update_gcc(size_t UTILS_UNUSED ignore) noexcept { // reset the object ssize_t size = 0; - void* array[NUM_FRAMES]; #if HAS_EXECINFO + void* array[NUM_FRAMES]; size = ::backtrace(array, NUM_FRAMES); size -= ignore; -#endif for (ssize_t i = 0; i < size; i++) { m_stack[i].pc = intptr_t(array[ignore + i]); } size--; // the last one seems to always be 0x0 +#endif // update how many frames we have m_frame_count = size_t(std::max(ssize_t(0), size)); @@ -112,7 +124,7 @@ utils::CString CallStack::demangle(const char* mangled) { #if !defined(NDEBUG) && !defined(WIN32) size_t len; int status; - std::unique_ptr demangled(abi::__cxa_demangle(mangled, nullptr, &len, &status)); + std::unique_ptr const demangled(abi::__cxa_demangle(mangled, nullptr, &len, &status)); if (!status && demangled) { // success return CString(demangled.get()); @@ -129,9 +141,9 @@ utils::CString CallStack::demangleTypeName(const char* mangled) { // ------------------------------------------------------------------------------------------------ -io::ostream& operator<<(io::ostream& stream, const CallStack& callstack) { +io::ostream& operator<<(io::ostream& stream, CallStack const& UTILS_UNUSED callstack) { #if HAS_EXECINFO - size_t size = callstack.getFrameCount(); + size_t const size = callstack.getFrameCount(); char buf[1024]; for (size_t i = 0; i < size; i++) { Dl_info info; @@ -152,7 +164,7 @@ io::ostream& operator<<(io::ostream& stream, const CallStack& callstack) { { char** symbols = ::backtrace_symbols(&pc, 1); stream << "#" << i << "\t" << symbols[0] << "\n"; - free(symbols); + free((void*)symbols); } } stream << io::endl; diff --git a/libs/utils/src/EntityManager.cpp b/libs/utils/src/EntityManager.cpp index 733eb4ec722..bdb12db3252 100644 --- a/libs/utils/src/EntityManager.cpp +++ b/libs/utils/src/EntityManager.cpp @@ -18,8 +18,12 @@ #include "EntityManagerImpl.h" +#include + namespace utils { +EntityManager::Listener::~Listener() noexcept = default; + EntityManager::EntityManager() : mGens(new uint8_t[RAW_INDEX_COUNT]) { // initialize all the generations to 0 @@ -30,12 +34,10 @@ EntityManager::~EntityManager() { delete [] mGens; } -EntityManager::Listener::~Listener() noexcept = default; - EntityManager& EntityManager::get() noexcept { // note: we leak the EntityManager because it's more important that it survives everything else - // the leak is really not a problem because the process is terminating anyways. - static EntityManagerImpl* instance = new EntityManagerImpl; + // the leak is really not a problem because the process is terminating anyway. + static EntityManagerImpl* instance = new(std::nothrow) EntityManagerImpl; return *instance; } @@ -55,6 +57,10 @@ void EntityManager::unregisterListener(EntityManager::Listener* l) noexcept { static_cast(this)->unregisterListener(l); } +size_t EntityManager::getEntityCount() const noexcept { + return static_cast(this)->getEntityCount(); +} + #if FILAMENT_UTILS_TRACK_ENTITIES std::vector EntityManager::getActiveEntities() const { return static_cast(this)->getActiveEntities(); diff --git a/libs/utils/src/EntityManagerImpl.h b/libs/utils/src/EntityManagerImpl.h index e9c7ef1699e..adf64a80c85 100644 --- a/libs/utils/src/EntityManagerImpl.h +++ b/libs/utils/src/EntityManagerImpl.h @@ -48,6 +48,16 @@ class UTILS_PRIVATE EntityManagerImpl : public EntityManager { using EntityManager::create; using EntityManager::destroy; + UTILS_NOINLINE + size_t getEntityCount() const noexcept { + std::lock_guard const lock(mFreeListLock); + if (mCurrentIndex < RAW_INDEX_COUNT) { + return (mCurrentIndex - 1) - mFreeList.size(); + } else { + return getMaxEntityCount() - mFreeList.size(); + } + } + UTILS_NOINLINE void create(size_t n, Entity* entities) { Entity::Type index{}; @@ -55,7 +65,7 @@ class UTILS_PRIVATE EntityManagerImpl : public EntityManager { uint8_t* const gens = mGens; // this must be thread-safe, acquire the free-list mutex - std::lock_guard lock(mFreeListLock); + std::lock_guard const lock(mFreeListLock); Entity::Type currentIndex = mCurrentIndex; for (size_t i = 0; i < n; i++) { // If we have more than a certain number of freed indices, get one from the list. @@ -106,7 +116,7 @@ class UTILS_PRIVATE EntityManagerImpl : public EntityManager { // against it. We don't guarantee anything about external state -- e.g. the listeners // will be called. if (isAlive(entities[i])) { - Entity::Type index = getIndex(entities[i]); + Entity::Type const index = getIndex(entities[i]); freeList.push_back(index); // The generation update doesn't require the lock because it's only used for isAlive() @@ -130,12 +140,12 @@ class UTILS_PRIVATE EntityManagerImpl : public EntityManager { } void registerListener(EntityManager::Listener* l) noexcept { - std::lock_guard lock(mListenerLock); + std::lock_guard const lock(mListenerLock); mListeners.insert(l); } void unregisterListener(EntityManager::Listener* l) noexcept { - std::lock_guard lock(mListenerLock); + std::lock_guard const lock(mListenerLock); mListeners.erase(l); } @@ -160,7 +170,7 @@ class UTILS_PRIVATE EntityManagerImpl : public EntityManager { private: utils::FixedCapacityVector getListeners() const noexcept { - std::lock_guard lock(mListenerLock); + std::lock_guard const lock(mListenerLock); tsl::robin_set const& listeners = mListeners; utils::FixedCapacityVector result(listeners.size()); result.resize(result.capacity()); // unfortunately this memset() diff --git a/libs/utils/src/JobSystem.cpp b/libs/utils/src/JobSystem.cpp index 00ada7c51b6..9473926a545 100644 --- a/libs/utils/src/JobSystem.cpp +++ b/libs/utils/src/JobSystem.cpp @@ -293,7 +293,7 @@ void JobSystem::wakeOne() noexcept { } inline JobSystem::ThreadState& JobSystem::getState() noexcept { - std::lock_guard lock(mThreadMapLock); + std::lock_guard lock(mThreadMapLock); auto iter = mThreadMap.find(std::this_thread::get_id()); ASSERT_PRECONDITION(iter != mThreadMap.end(), "This thread has not been adopted."); return *iter->second; @@ -585,7 +585,7 @@ void JobSystem::runAndWait(JobSystem::Job*& job) noexcept { void JobSystem::adopt() { const auto tid = std::this_thread::get_id(); - std::unique_lock lock(mThreadMapLock); + std::unique_lock lock(mThreadMapLock); auto iter = mThreadMap.find(tid); ThreadState* const state = iter == mThreadMap.end() ? nullptr : iter->second; lock.unlock(); @@ -618,7 +618,7 @@ void JobSystem::adopt() { void JobSystem::emancipate() { const auto tid = std::this_thread::get_id(); - std::lock_guard lock(mThreadMapLock); + std::lock_guard lock(mThreadMapLock); auto iter = mThreadMap.find(tid); ThreadState* const state = iter == mThreadMap.end() ? nullptr : iter->second; ASSERT_PRECONDITION(state, "this thread is not an adopted thread"); diff --git a/libs/utils/src/Log.cpp b/libs/utils/src/Log.cpp index 57c2d2355f3..86eaa5531ca 100644 --- a/libs/utils/src/Log.cpp +++ b/libs/utils/src/Log.cpp @@ -31,6 +31,8 @@ #include #endif +#include + namespace utils { namespace io { @@ -46,43 +48,41 @@ class LogStream : public ostream { ostream& flush() noexcept override; private: - Priority mPriority; + const Priority mPriority; }; ostream& LogStream::flush() noexcept { - std::lock_guard lock(mImpl->mLock); + std::lock_guard const lock(mImpl->mLock); Buffer& buf = getBuffer(); + char const* const str = buf.get(); + if (UTILS_UNLIKELY(!str)) { + // this can happen if the log hasn't been written ever + return *this; + } + #ifdef __ANDROID__ + int prio = ANDROID_LOG_UNKNOWN; switch (mPriority) { - case LOG_DEBUG: - __android_log_write(ANDROID_LOG_DEBUG, UTILS_LOG_TAG, buf.get()); - break; - case LOG_ERROR: - __android_log_write(ANDROID_LOG_ERROR, UTILS_LOG_TAG, buf.get()); - break; - case LOG_WARNING: - __android_log_write(ANDROID_LOG_WARN, UTILS_LOG_TAG, buf.get()); - break; - case LOG_INFO: - __android_log_write(ANDROID_LOG_INFO, UTILS_LOG_TAG, buf.get()); - break; - case LOG_VERBOSE: - __android_log_write(ANDROID_LOG_VERBOSE, UTILS_LOG_TAG, buf.get()); - break; + case LOG_DEBUG: prio = ANDROID_LOG_DEBUG; break; + case LOG_ERROR: prio = ANDROID_LOG_ERROR; break; + case LOG_WARNING: prio = ANDROID_LOG_WARN; break; + case LOG_INFO: prio = ANDROID_LOG_INFO; break; + case LOG_VERBOSE: prio = ANDROID_LOG_VERBOSE; break; } + __android_log_write(prio, UTILS_LOG_TAG, str); #elif defined(__EMSCRIPTEN__) switch (mPriority) { case LOG_DEBUG: case LOG_WARNING: case LOG_INFO: - _emscripten_out(buf.get()); + _emscripten_out(str); break; case LOG_ERROR: - _emscripten_err(buf.get()); + _emscripten_err(str); break; case LOG_VERBOSE: #ifndef NFIL_DEBUG - _emscripten_out(buf.get()); + _emscripten_out(str); #endif break; } @@ -91,14 +91,14 @@ ostream& LogStream::flush() noexcept { case LOG_DEBUG: case LOG_WARNING: case LOG_INFO: - fprintf(stdout, "%s", buf.get()); + fprintf(stdout, "%s", str); break; case LOG_ERROR: - fprintf(stderr, "%s", buf.get()); + fprintf(stderr, "%s", str); break; case LOG_VERBOSE: #ifndef NDEBUG - fprintf(stdout, "%s", buf.get()); + fprintf(stdout, "%s", str); #endif break; } diff --git a/libs/utils/src/NameComponentManager.cpp b/libs/utils/src/NameComponentManager.cpp index 98bb3839a32..eae5df4a163 100644 --- a/libs/utils/src/NameComponentManager.cpp +++ b/libs/utils/src/NameComponentManager.cpp @@ -21,7 +21,7 @@ namespace utils { static constexpr size_t NAME = 0; -NameComponentManager::NameComponentManager(EntityManager& em) { +NameComponentManager::NameComponentManager(EntityManager&) { } NameComponentManager::~NameComponentManager() = default; @@ -36,14 +36,6 @@ const char* NameComponentManager::getName(Instance instance) const noexcept { return elementAt(instance).c_str(); } -size_t NameComponentManager::getComponentCount() const noexcept { - return SingleInstanceComponentManager::getComponentCount(); -} - -Entity const* NameComponentManager::getEntities() const noexcept { - return SingleInstanceComponentManager::getEntities(); -} - void NameComponentManager::addComponent(Entity e) { SingleInstanceComponentManager::addComponent(e); } @@ -52,8 +44,4 @@ void NameComponentManager::removeComponent(Entity e) { SingleInstanceComponentManager::removeComponent(e); } -void NameComponentManager::gc(const EntityManager& em, size_t ratio) noexcept { - SingleInstanceComponentManager::gc(em, ratio); -} - } // namespace utils diff --git a/libs/utils/src/Panic.cpp b/libs/utils/src/Panic.cpp index 023a79b2477..addec296c10 100644 --- a/libs/utils/src/Panic.cpp +++ b/libs/utils/src/Panic.cpp @@ -14,17 +14,64 @@ * limitations under the License. */ +#include #include +#include +#include -#include -#include #include #include +#include +#include +#include +#include -#include +#include +#include +#include +#include namespace utils { +// ------------------------------------------------------------------------------------------------ + +class UserPanicHandler { + struct CallBack { + Panic::PanicHandlerCallback handler = nullptr; + void* user = nullptr; + void call(Panic const& panic) const noexcept { + if (UTILS_UNLIKELY(handler)) { + handler(user, panic); + } + } + }; + + mutable std::mutex mLock{}; + CallBack mCallBack{}; + + CallBack getCallback() const noexcept { + std::lock_guard const lock(mLock); + return mCallBack; + } + +public: + static UserPanicHandler& get() noexcept { + static UserPanicHandler data; + return data; + } + + void call(Panic const& panic) const noexcept { + getCallback().call(panic); + } + + void set(Panic::PanicHandlerCallback handler, void* user) noexcept { + std::lock_guard const lock(mLock); + mCallBack = { handler, user }; + } +}; + +// ------------------------------------------------------------------------------------------------ + static std::string formatString(const char* format, va_list args) noexcept { std::string reason; @@ -35,10 +82,12 @@ static std::string formatString(const char* format, va_list args) noexcept { if (n >= 0) { ++n; // for the nul-terminating char - char* buf = new char[n]; - vsnprintf(buf, size_t(n), format, args); - reason.assign(buf); - delete [] buf; + char* const buf = new(std::nothrow) char[n]; + if (buf) { + vsnprintf(buf, size_t(n), format, args); + reason.assign(buf); + delete [] buf; + } } return reason; } @@ -63,8 +112,16 @@ static std::string panicString( #endif } +// ------------------------------------------------------------------------------------------------ + Panic::~Panic() noexcept = default; +void Panic::setPanicHandler(PanicHandlerCallback handler, void* user) noexcept { + UserPanicHandler::get().set(handler, user); +} + +// ------------------------------------------------------------------------------------------------ + template TPanic::TPanic(std::string reason) : m_reason(std::move(reason)) { @@ -88,6 +145,11 @@ const char* TPanic::what() const noexcept { return m_msg.c_str(); } +template +const char* TPanic::getReason() const noexcept { + return m_reason.c_str(); +} + template const char* TPanic::getFunction() const noexcept { return m_function; @@ -135,13 +197,22 @@ template void TPanic::panic(char const* function, char const* file, int line, const char* format, ...) { va_list args; va_start(args, format); - std::string reason(formatString(format, args)); + std::string const reason(formatString(format, args)); va_end(args); T e(function, formatFile(file), line, reason); + + // always log the Panic at the point it is detected e.log(); + + // Call the user provided handler + UserPanicHandler::get().call(e); + + // if exceptions are enabled, throw now. #ifdef __EXCEPTIONS - throw e; + throw e; #endif + + // and finally abort if we somehow get here std::abort(); } @@ -152,7 +223,7 @@ namespace details { void panicLog(char const* function, char const* file, int line, const char* format, ...) noexcept { va_list args; va_start(args, format); - std::string reason(formatString(format, args)); + std::string const reason(formatString(format, args)); va_end(args); const std::string msg = panicString("" /* no extra message */, diff --git a/libs/utils/src/debug.cpp b/libs/utils/src/debug.cpp index f033770a01f..3d20475ad71 100644 --- a/libs/utils/src/debug.cpp +++ b/libs/utils/src/debug.cpp @@ -18,6 +18,8 @@ #include +#include + namespace utils { // we use a non-inlined, not marked as "no return" function for aborting so that we can set diff --git a/libs/utils/src/linux/Path.cpp b/libs/utils/src/linux/Path.cpp index 46d19dcb2cc..ebc4502d1d1 100644 --- a/libs/utils/src/linux/Path.cpp +++ b/libs/utils/src/linux/Path.cpp @@ -22,6 +22,8 @@ #include #include +#include + namespace utils { bool Path::mkdir() const { diff --git a/libs/utils/src/ostream.cpp b/libs/utils/src/ostream.cpp index 336c36dd97d..35ffab84347 100644 --- a/libs/utils/src/ostream.cpp +++ b/libs/utils/src/ostream.cpp @@ -24,10 +24,14 @@ #include #include +#include #include #include +#include +#include #include +#include template class utils::PrivateImplementation; @@ -35,6 +39,34 @@ namespace utils::io { ostream::~ostream() = default; +void ostream::setConsumer(ConsumerCallback consumer, void* user) noexcept { + auto* const pImpl = mImpl; + std::lock_guard const lock(pImpl->mLock); + pImpl->mConsumer = { consumer, user }; +} + +ostream& flush(ostream& s) noexcept { + auto* const pImpl = s.mImpl; + pImpl->mLock.lock(); + auto const callback = pImpl->mConsumer; + if (UTILS_UNLIKELY(callback.first)) { + auto& buf = s.getBuffer(); + char const* const data = buf.get(); + if (UTILS_LIKELY(data)) { + char* const str = strdup(data); + buf.reset(); + pImpl->mLock.unlock(); + // call ConsumerCallback without lock held + callback.first(callback.second, str); + ::free(str); + return s; + } + } + pImpl->mLock.unlock(); + + return s.flush(); +} + ostream::Buffer& ostream::getBuffer() noexcept { return mImpl->mData; } @@ -69,12 +101,12 @@ ostream& ostream::print(const char* format, ...) noexcept { // figure out how much size to we need va_start(args0, format); va_copy(args1, args0); - ssize_t s = vsnprintf(nullptr, 0, format, args0); + ssize_t const s = vsnprintf(nullptr, 0, format, args0); va_end(args0); { // scope for the lock - std::lock_guard lock(mImpl->mLock); + std::lock_guard const lock(mImpl->mLock); Buffer& buf = getBuffer(); @@ -205,14 +237,14 @@ ostream::Buffer::~Buffer() noexcept { void ostream::Buffer::advance(ssize_t n) noexcept { if (n > 0) { - size_t written = n < size ? size_t(n) : size; + size_t const written = n < size ? size_t(n) : size; curr += written; size -= written; } } void ostream::Buffer::reserve(size_t newSize) noexcept { - size_t offset = curr - buffer; + size_t const offset = curr - buffer; if (buffer == nullptr) { buffer = (char*)malloc(newSize); } else { @@ -235,10 +267,14 @@ 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 used = curr - buffer; - size_t newCapacity = std::max(size_t(32), used + (s * 3 + 1) / 2); // 32 bytes minimum + size_t const used = curr - buffer; + size_t const newCapacity = std::max(size_t(32), used + (s * 3 + 1) / 2); // 32 bytes minimum reserve(newCapacity); assert(size >= s); } diff --git a/libs/utils/src/ostream_.h b/libs/utils/src/ostream_.h index a417cde70bb..19f29572878 100644 --- a/libs/utils/src/ostream_.h +++ b/libs/utils/src/ostream_.h @@ -25,6 +25,7 @@ namespace utils::io { struct ostream_ { std::mutex mLock; ostream::Buffer mData; + std::pair mConsumer{}; bool mShowHex = false; }; 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_CString.cpp b/libs/utils/test/test_CString.cpp index 759f30a4527..a19de9b7a45 100644 --- a/libs/utils/test/test_CString.cpp +++ b/libs/utils/test/test_CString.cpp @@ -93,3 +93,25 @@ TEST(CString, ReplacePastEndOfString) { EXPECT_STREQ("foo bar bat", str.c_str()); } } + +TEST(FixedSizeString, EmptyString) { + { + FixedSizeString<32> str; + EXPECT_STREQ("", str.c_str()); + } + { + FixedSizeString<32> str(""); + EXPECT_STREQ("", str.c_str()); + } +} + +TEST(FixedSizeString, Constructors) { + { + FixedSizeString<32> str("short string"); + EXPECT_STREQ("short string", str.c_str()); + } + { + FixedSizeString<16> str("a long string abcdefghijklmnopqrst"); + EXPECT_STREQ("a long string a", str.c_str()); + } +} diff --git a/libs/utils/test/test_FixedCircularBuffer.cpp b/libs/utils/test/test_FixedCircularBuffer.cpp new file mode 100644 index 00000000000..8749dfb89a1 --- /dev/null +++ b/libs/utils/test/test_FixedCircularBuffer.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include + +using namespace utils; + +TEST(FixedCircularBufferTest, Simple) { + FixedCircularBuffer circularBuffer(4); + EXPECT_EQ(circularBuffer.size(), 0); + + EXPECT_FALSE(circularBuffer.push(1).has_value()); + EXPECT_FALSE(circularBuffer.push(2).has_value()); + EXPECT_FALSE(circularBuffer.push(3).has_value()); + EXPECT_EQ(circularBuffer.size(), 3); + EXPECT_EQ(circularBuffer.pop(), 1); + EXPECT_EQ(circularBuffer.pop(), 2); + EXPECT_EQ(circularBuffer.pop(), 3); + EXPECT_EQ(circularBuffer.size(), 0); + + EXPECT_FALSE(circularBuffer.push(4).has_value()); + EXPECT_FALSE(circularBuffer.push(5).has_value()); + EXPECT_FALSE(circularBuffer.push(6).has_value()); + EXPECT_FALSE(circularBuffer.push(7).has_value()); + EXPECT_EQ(circularBuffer.size(), 4); + EXPECT_TRUE(circularBuffer.full()); + EXPECT_EQ(circularBuffer.pop(), 4); + EXPECT_EQ(circularBuffer.pop(), 5); + EXPECT_EQ(circularBuffer.pop(), 6); + EXPECT_EQ(circularBuffer.pop(), 7); +} + +TEST(FixedCircularBufferTest, Displace) { + FixedCircularBuffer circularBuffer(4); + EXPECT_EQ(circularBuffer.size(), 0); + EXPECT_FALSE(circularBuffer.push(1).has_value()); + EXPECT_FALSE(circularBuffer.push(2).has_value()); + EXPECT_FALSE(circularBuffer.push(3).has_value()); + EXPECT_FALSE(circularBuffer.push(4).has_value()); + EXPECT_TRUE(circularBuffer.full()); + + { + auto v = circularBuffer.push(5); + EXPECT_EQ(v.value(), 1); + } + { + auto v = circularBuffer.push(6); + EXPECT_EQ(v.value(), 2); + } + EXPECT_TRUE(circularBuffer.full()); + + EXPECT_EQ(circularBuffer.pop(), 3); + EXPECT_EQ(circularBuffer.size(), 3); +} + +TEST(FixedCircularBufferTest, ZeroCapacity) { + FixedCircularBuffer circularBuffer(0); + EXPECT_EQ(circularBuffer.size(), 0); + EXPECT_EQ(circularBuffer.full(), false); + + auto v = circularBuffer.push(1); + EXPECT_EQ(v.value(), 1); + EXPECT_EQ(circularBuffer.size(), 0); + EXPECT_EQ(circularBuffer.full(), false); +} + +TEST(FixedCircularBufferTest, Exceptions) { +#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST) + FixedCircularBuffer circularBuffer(4); + + EXPECT_DEATH({ + circularBuffer.pop(); // should assert + }, "failed assertion"); + + circularBuffer.push(1); + circularBuffer.push(2); + circularBuffer.push(3); + circularBuffer.push(4); + circularBuffer.push(5); // should not assert +#endif +} diff --git a/libs/utils/test/test_sstream.cpp b/libs/utils/test/test_sstream.cpp index 9886f9d96ec..9a832faf2ed 100644 --- a/libs/utils/test/test_sstream.cpp +++ b/libs/utils/test/test_sstream.cpp @@ -18,8 +18,33 @@ #include +#include + +using namespace utils; using namespace utils::io; +TEST(ostream, setConsumer) { + slog.d.setConsumer(+[](void*, char const*) { + GTEST_FAIL(); + }, nullptr); + // we test that we don't crash if the log is empty and that we don't call the consumer. + flush(slog.d); + + slog.d.setConsumer(nullptr, nullptr); + slog.d << "hello world"; + // we test that after resetting the consumer, it's not called on flush. + flush(slog.d); + + const char* str = "hello world!"; + slog.d.setConsumer(+[](void* user, char const* str) { + ASSERT_STREQ(str, (const char*)user); + }, (void*)str); + slog.d << str; + // we test that the comsumer is called with the right string + flush(slog.d); + slog.d.setConsumer(nullptr, nullptr); +} + TEST(sstream, EmptyString) { sstream ss; EXPECT_STREQ("", ss.c_str()); @@ -119,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) { @@ -133,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); @@ -157,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/libs/viewer/include/viewer/Settings.h b/libs/viewer/include/viewer/Settings.h index fc46d2887ed..2094296bd75 100644 --- a/libs/viewer/include/viewer/Settings.h +++ b/libs/viewer/include/viewer/Settings.h @@ -59,7 +59,8 @@ enum class ToneMapping : uint8_t { FILMIC = 3, AGX = 4, GENERIC = 5, - DISPLAY_RANGE = 6, + PBR_NEUTRAL = 6, + DISPLAY_RANGE = 7, }; using AmbientOcclusionOptions = filament::View::AmbientOcclusionOptions; @@ -77,6 +78,7 @@ using TemporalAntiAliasingOptions = filament::View::TemporalAntiAliasingOptions; using VignetteOptions = filament::View::VignetteOptions; using VsmShadowOptions = filament::View::VsmShadowOptions; using GuardBandOptions = filament::View::GuardBandOptions; +using StereoscopicOptions = filament::View::StereoscopicOptions; using LightManager = filament::LightManager; // These functions push all editable property values to their respective Filament objects. @@ -192,6 +194,7 @@ struct ViewSettings { VignetteOptions vignette; VsmShadowOptions vsmShadowOptions; GuardBandOptions guardBand; + StereoscopicOptions stereoscopicOptions; // Custom View Options ColorGradingSettings colorGrading; @@ -231,6 +234,8 @@ struct ViewerOptions { float cameraISO = 100.0f; float cameraNear = 0.1f; float cameraFar = 100.0f; + float cameraEyeOcularDistance = 0.0f; + float cameraEyeToeIn = 0.0f; float groundShadowStrength = 0.75f; bool groundPlaneEnabled = false; bool skyboxEnabled = true; diff --git a/libs/viewer/include/viewer/ViewerGui.h b/libs/viewer/include/viewer/ViewerGui.h index 6d6926a9aab..8f84335747c 100644 --- a/libs/viewer/include/viewer/ViewerGui.h +++ b/libs/viewer/include/viewer/ViewerGui.h @@ -231,8 +231,6 @@ class UTILS_PUBLIC ViewerGui { int getCurrentCamera() const { return mCurrentCamera; } - float getOcularDistance() const { return mOcularDistance; } - private: using SceneMask = gltfio::NodeManager::SceneMask; @@ -268,9 +266,6 @@ class UTILS_PUBLIC ViewerGui { SceneMask mVisibleScenes; bool mShowingRestPose = false; - // Stereoscopic debugging - float mOcularDistance = 0.0f; - // 0 is the default "free camera". Additional cameras come from the gltf file (1-based index). int mCurrentCamera = 0; diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index 22b64588b09..639d07a2c93 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -88,6 +88,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ToneMapp else if (0 == compare(tokens[i], jsonChunk, "FILMIC")) { *out = ToneMapping::FILMIC; } else if (0 == compare(tokens[i], jsonChunk, "AGX")) { *out = ToneMapping::AGX; } else if (0 == compare(tokens[i], jsonChunk, "GENERIC")) { *out = ToneMapping::GENERIC; } + else if (0 == compare(tokens[i], jsonChunk, "PBR_NEUTRAL")) { *out = ToneMapping::PBR_NEUTRAL; } else if (0 == compare(tokens[i], jsonChunk, "DISPLAY_RANGE")) { *out = ToneMapping::DISPLAY_RANGE; } else { slog.w << "Invalid ToneMapping: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; @@ -302,6 +303,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewSett i = parse(tokens, i + 1, jsonChunk, &out->vsmShadowOptions); } else if (compare(tok, jsonChunk, "postProcessingEnabled") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->postProcessingEnabled); + } else if (compare(tok, jsonChunk, "stereoscopicOptions") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->stereoscopicOptions); } else { slog.w << "Invalid view setting key: '" << STR(tok, jsonChunk) << "'" << io::endl; i = parse(tokens, i + 1); @@ -500,6 +503,10 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewerOp i = parse(tokens, i + 1, jsonChunk, &out->cameraNear); } else if (compare(tok, jsonChunk, "cameraFar") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->cameraFar); + } else if (compare(tok, jsonChunk, "cameraEyeOcularDistance") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeOcularDistance); + } else if (compare(tok, jsonChunk, "cameraEyeToeIn") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeToeIn); } else if (compare(tok, jsonChunk, "groundShadowStrength") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->groundShadowStrength); } else if (compare(tok, jsonChunk, "groundPlaneEnabled") == 0) { @@ -572,6 +579,7 @@ void applySettings(Engine* engine, const ViewSettings& settings, View* dest) { dest->setShadowType(settings.shadowType); dest->setVsmShadowOptions(settings.vsmShadowOptions); dest->setGuardBandOptions(settings.guardBand); + dest->setStereoscopicOptions(settings.stereoscopicOptions); dest->setPostProcessingEnabled(settings.postProcessingEnabled); } @@ -612,8 +620,10 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight* } for (size_t i = 0; i < sceneLightCount; i++) { auto const li = lm->getInstance(sceneLights[i]); - lm->setShadowCaster(li, settings.enableShadows); - lm->setShadowOptions(li, settings.shadowOptions); + if (li) { + lm->setShadowCaster(li, settings.enableShadows); + lm->setShadowOptions(li, settings.shadowOptions); + } } view->setSoftShadowOptions(settings.softShadowOptions); } @@ -644,6 +654,22 @@ void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera camera->setFocusDistance(settings.cameraFocusDistance); } engine->setAutomaticInstancingEnabled(settings.autoInstancingEnabled); + + // Eyes are rendered from left-to-right, i.e., eye 0 is rendered to the left side of the + // window. + // For testing, we want to render a side-by-side layout so users can view with + // "cross-eyed" stereo. + // For cross-eyed stereo, Eye 0 is really the RIGHT eye, while Eye 1 is the LEFT eye. + const auto od = settings.cameraEyeOcularDistance; + const auto toeIn = settings.cameraEyeToeIn; + const auto eyeCount = engine->getConfig().stereoscopicEyeCount; + const double3 up = double3(0.0, 1.0, 0.0); + const mat4 rightEye = mat4::translation(double3{ od, 0.0, 0.0}) * mat4::rotation( toeIn, up); + const mat4 leftEye = mat4::translation(double3{-od, 0.0, 0.0}) * mat4::rotation(-toeIn, up); + const mat4 modelMatrices[2] = { rightEye, leftEye }; + for (int i = 0; i < eyeCount; i++) { + camera->setEyeModelMatrix(i, modelMatrices[i % 2]); + } } constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noexcept { @@ -654,11 +680,12 @@ constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noe case ToneMapping::FILMIC: return new FilmicToneMapper; case ToneMapping::AGX: return new AgxToneMapper(settings.agxToneMapper.look); case ToneMapping::GENERIC: return new GenericToneMapper( - settings.genericToneMapper.contrast, - settings.genericToneMapper.midGrayIn, - settings.genericToneMapper.midGrayOut, - settings.genericToneMapper.hdrMax + settings.genericToneMapper.contrast, + settings.genericToneMapper.midGrayIn, + settings.genericToneMapper.midGrayOut, + settings.genericToneMapper.hdrMax ); + case ToneMapping::PBR_NEUTRAL: return new PBRNeutralToneMapper; case ToneMapping::DISPLAY_RANGE: return new DisplayRangeToneMapper; } } @@ -709,6 +736,7 @@ static std::ostream& operator<<(std::ostream& out, ToneMapping in) { case ToneMapping::FILMIC: return out << "\"FILMIC\""; case ToneMapping::AGX: return out << "\"AGX\""; case ToneMapping::GENERIC: return out << "\"GENERIC\""; + case ToneMapping::PBR_NEUTRAL: return out << "\"PBR_NEUTRAL\""; case ToneMapping::DISPLAY_RANGE: return out << "\"DISPLAY_RANGE\""; } return out << "\"INVALID\""; @@ -855,6 +883,8 @@ static std::ostream& operator<<(std::ostream& out, const ViewerOptions& in) { << "\"cameraISO\": " << (in.cameraISO) << ",\n" << "\"cameraNear\": " << (in.cameraNear) << ",\n" << "\"cameraFar\": " << (in.cameraFar) << ",\n" + << "\"cameraEyeOcularDistance\": " << (in.cameraEyeOcularDistance) << ",\n" + << "\"cameraEyeToeIn\": " << (in.cameraEyeToeIn) << ",\n" << "\"groundShadowStrength\": " << (in.groundShadowStrength) << ",\n" << "\"groundPlaneEnabled\": " << to_string(in.groundPlaneEnabled) << ",\n" << "\"skyboxEnabled\": " << to_string(in.skyboxEnabled) << ",\n" @@ -892,6 +922,7 @@ static std::ostream& operator<<(std::ostream& out, const ViewSettings& in) { << "\"shadowType\": " << (in.shadowType) << ",\n" << "\"vsmShadowOptions\": " << (in.vsmShadowOptions) << ",\n" << "\"guardBand\": " << (in.guardBand) << ",\n" + << "\"stereoscopicOptions\": " << (in.stereoscopicOptions) << ",\n" << "\"postProcessingEnabled\": " << to_string(in.postProcessingEnabled) << "\n" << "}"; } diff --git a/libs/viewer/src/Settings_generated.cpp b/libs/viewer/src/Settings_generated.cpp index 3a08774926a..3b7c5f6236e 100644 --- a/libs/viewer/src/Settings_generated.cpp +++ b/libs/viewer/src/Settings_generated.cpp @@ -391,6 +391,8 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DepthOfFieldOpt CHECK_KEY(tok); if (compare(tok, jsonChunk, "cocScale") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->cocScale); + } else if (compare(tok, jsonChunk, "cocAspectRatio") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->cocAspectRatio); } else if (compare(tok, jsonChunk, "maxApertureDiameter") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->maxApertureDiameter); } else if (compare(tok, jsonChunk, "enabled") == 0) { @@ -424,6 +426,7 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DepthOfFieldOpt std::ostream& operator<<(std::ostream& out, const DepthOfFieldOptions& in) { return out << "{\n" << "\"cocScale\": " << (in.cocScale) << ",\n" + << "\"cocAspectRatio\": " << (in.cocAspectRatio) << ",\n" << "\"maxApertureDiameter\": " << (in.maxApertureDiameter) << ",\n" << "\"enabled\": " << to_string(in.enabled) << ",\n" << "\"filter\": " << (in.filter) << ",\n" @@ -647,6 +650,67 @@ std::ostream& operator<<(std::ostream& out, const MultiSampleAntiAliasingOptions << "}"; } +int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxType* out) { + if (0 == compare(tokens[i], jsonChunk, "AABB")) { *out = TemporalAntiAliasingOptions::BoxType::AABB; } + else if (0 == compare(tokens[i], jsonChunk, "VARIANCE")) { *out = TemporalAntiAliasingOptions::BoxType::VARIANCE; } + else if (0 == compare(tokens[i], jsonChunk, "AABB_VARIANCE")) { *out = TemporalAntiAliasingOptions::BoxType::AABB_VARIANCE; } + else { + slog.w << "Invalid TemporalAntiAliasingOptions::BoxType: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxType in) { + switch (in) { + case TemporalAntiAliasingOptions::BoxType::AABB: return out << "\"AABB\""; + case TemporalAntiAliasingOptions::BoxType::VARIANCE: return out << "\"VARIANCE\""; + case TemporalAntiAliasingOptions::BoxType::AABB_VARIANCE: return out << "\"AABB_VARIANCE\""; + } + return out << "\"INVALID\""; +} + +int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxClipping* out) { + if (0 == compare(tokens[i], jsonChunk, "ACCURATE")) { *out = TemporalAntiAliasingOptions::BoxClipping::ACCURATE; } + else if (0 == compare(tokens[i], jsonChunk, "CLAMP")) { *out = TemporalAntiAliasingOptions::BoxClipping::CLAMP; } + else if (0 == compare(tokens[i], jsonChunk, "NONE")) { *out = TemporalAntiAliasingOptions::BoxClipping::NONE; } + else { + slog.w << "Invalid TemporalAntiAliasingOptions::BoxClipping: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxClipping in) { + switch (in) { + case TemporalAntiAliasingOptions::BoxClipping::ACCURATE: return out << "\"ACCURATE\""; + case TemporalAntiAliasingOptions::BoxClipping::CLAMP: return out << "\"CLAMP\""; + case TemporalAntiAliasingOptions::BoxClipping::NONE: return out << "\"NONE\""; + } + return out << "\"INVALID\""; +} + +int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::JitterPattern* out) { + if (0 == compare(tokens[i], jsonChunk, "RGSS_X4")) { *out = TemporalAntiAliasingOptions::JitterPattern::RGSS_X4; } + else if (0 == compare(tokens[i], jsonChunk, "UNIFORM_HELIX_X4")) { *out = TemporalAntiAliasingOptions::JitterPattern::UNIFORM_HELIX_X4; } + else if (0 == compare(tokens[i], jsonChunk, "HALTON_23_X8")) { *out = TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X8; } + else if (0 == compare(tokens[i], jsonChunk, "HALTON_23_X16")) { *out = TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X16; } + else if (0 == compare(tokens[i], jsonChunk, "HALTON_23_X32")) { *out = TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X32; } + else { + slog.w << "Invalid TemporalAntiAliasingOptions::JitterPattern: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::JitterPattern in) { + switch (in) { + case TemporalAntiAliasingOptions::JitterPattern::RGSS_X4: return out << "\"RGSS_X4\""; + case TemporalAntiAliasingOptions::JitterPattern::UNIFORM_HELIX_X4: return out << "\"UNIFORM_HELIX_X4\""; + case TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X8: return out << "\"HALTON_23_X8\""; + case TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X16: return out << "\"HALTON_23_X16\""; + case TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X32: return out << "\"HALTON_23_X32\""; + } + return out << "\"INVALID\""; +} + int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions* out) { CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i++].size; @@ -657,8 +721,32 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAli i = parse(tokens, i + 1, jsonChunk, &out->filterWidth); } else if (compare(tok, jsonChunk, "feedback") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->feedback); + } else if (compare(tok, jsonChunk, "lodBias") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->lodBias); + } else if (compare(tok, jsonChunk, "sharpness") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->sharpness); } else if (compare(tok, jsonChunk, "enabled") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else if (compare(tok, jsonChunk, "upscaling") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->upscaling); + } else if (compare(tok, jsonChunk, "filterHistory") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->filterHistory); + } else if (compare(tok, jsonChunk, "filterInput") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->filterInput); + } else if (compare(tok, jsonChunk, "useYCoCg") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->useYCoCg); + } else if (compare(tok, jsonChunk, "boxType") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->boxType); + } else if (compare(tok, jsonChunk, "boxClipping") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->boxClipping); + } else if (compare(tok, jsonChunk, "jitterPattern") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->jitterPattern); + } else if (compare(tok, jsonChunk, "varianceGamma") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->varianceGamma); + } else if (compare(tok, jsonChunk, "preventFlickering") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->preventFlickering); + } else if (compare(tok, jsonChunk, "historyReprojection") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->historyReprojection); } else { slog.w << "Invalid TemporalAntiAliasingOptions key: '" << STR(tok, jsonChunk) << "'" << io::endl; i = parse(tokens, i + 1); @@ -675,7 +763,19 @@ std::ostream& operator<<(std::ostream& out, const TemporalAntiAliasingOptions& i return out << "{\n" << "\"filterWidth\": " << (in.filterWidth) << ",\n" << "\"feedback\": " << (in.feedback) << ",\n" - << "\"enabled\": " << to_string(in.enabled) << "\n" + << "\"lodBias\": " << (in.lodBias) << ",\n" + << "\"sharpness\": " << (in.sharpness) << ",\n" + << "\"enabled\": " << to_string(in.enabled) << ",\n" + << "\"upscaling\": " << to_string(in.upscaling) << ",\n" + << "\"filterHistory\": " << to_string(in.filterHistory) << ",\n" + << "\"filterInput\": " << to_string(in.filterInput) << ",\n" + << "\"useYCoCg\": " << to_string(in.useYCoCg) << ",\n" + << "\"boxType\": " << (in.boxType) << ",\n" + << "\"boxClipping\": " << (in.boxClipping) << ",\n" + << "\"jitterPattern\": " << (in.jitterPattern) << ",\n" + << "\"varianceGamma\": " << (in.varianceGamma) << ",\n" + << "\"preventFlickering\": " << to_string(in.preventFlickering) << ",\n" + << "\"historyReprojection\": " << to_string(in.historyReprojection) << "\n" << "}"; } diff --git a/libs/viewer/src/Settings_generated.h b/libs/viewer/src/Settings_generated.h index e5ac7f8df96..1215a147eee 100644 --- a/libs/viewer/src/Settings_generated.h +++ b/libs/viewer/src/Settings_generated.h @@ -69,6 +69,15 @@ std::ostream& operator<<(std::ostream& out, const AmbientOcclusionOptions& in); int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, MultiSampleAntiAliasingOptions* out); std::ostream& operator<<(std::ostream& out, const MultiSampleAntiAliasingOptions& in); +int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxType* out); +std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxType in); + +int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxClipping* out); +std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxClipping in); + +int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::JitterPattern* out); +std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::JitterPattern in); + int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions* out); std::ostream& operator<<(std::ostream& out, const TemporalAntiAliasingOptions& in); diff --git a/libs/viewer/src/ViewerGui.cpp b/libs/viewer/src/ViewerGui.cpp index 8e0177538dc..216288d3f86 100644 --- a/libs/viewer/src/ViewerGui.cpp +++ b/libs/viewer/src/ViewerGui.cpp @@ -145,6 +145,9 @@ static void computeToneMapPlot(ColorGradingSettings& settings, float* plot) { ); hdrMax = settings.genericToneMapper.hdrMax; break; + case ToneMapping::PBR_NEUTRAL: + mapper = new PBRNeutralToneMapper; + break; case ToneMapping::DISPLAY_RANGE: mapper = new DisplayRangeToneMapper; break; @@ -196,7 +199,7 @@ static void colorGradingUI(Settings& settings, float* rangePlot, float* curvePlo int toneMapping = (int) colorGrading.toneMapping; ImGui::Combo("Tone-mapping", &toneMapping, - "Linear\0ACES (legacy)\0ACES\0Filmic\0AgX\0Generic\0Display Range\0\0"); + "Linear\0ACES (legacy)\0ACES\0Filmic\0AgX\0Generic\0PBR Neutral\0Display Range\0\0"); colorGrading.toneMapping = (decltype(colorGrading.toneMapping)) toneMapping; if (colorGrading.toneMapping == ToneMapping::GENERIC) { if (ImGui::CollapsingHeader("Tonemap parameters")) { @@ -792,6 +795,29 @@ void ViewerGui::updateUserInterface() { ImGui::Checkbox("Lens Flare", &mSettings.view.bloom.lensFlare); } + if (ImGui::CollapsingHeader("TAA Options")) { + ImGui::Checkbox("Upscaling", &mSettings.view.taa.upscaling); + ImGui::Checkbox("History Reprojection", &mSettings.view.taa.historyReprojection); + ImGui::SliderFloat("Feedback", &mSettings.view.taa.feedback, 0.0f, 1.0f); + ImGui::Checkbox("Filter History", &mSettings.view.taa.filterHistory); + ImGui::Checkbox("Filter Input", &mSettings.view.taa.filterInput); + ImGui::SliderFloat("FilterWidth", &mSettings.view.taa.filterWidth, 0.2f, 2.0f); + ImGui::SliderFloat("LOD bias", &mSettings.view.taa.lodBias, -8.0f, 0.0f); + ImGui::Checkbox("Use YCoCg", &mSettings.view.taa.useYCoCg); + ImGui::Checkbox("Prevent Flickering", &mSettings.view.taa.preventFlickering); + int jitterSequence = (int)mSettings.view.taa.jitterPattern; + int boxClipping = (int)mSettings.view.taa.boxClipping; + int boxType = (int)mSettings.view.taa.boxType; + ImGui::Combo("Jitter Pattern", &jitterSequence, "RGSS x4\0Uniform Helix x4\0Halton x8\0Halton x16\0Halton x32\0\0"); + ImGui::Combo("Box Clipping", &boxClipping, "Accurate\0Clamp\0None\0\0"); + ImGui::Combo("Box Type", &boxType, "AABB\0Variance\0Both\0\0"); + ImGui::SliderFloat("Variance Gamma", &mSettings.view.taa.varianceGamma, 0.75f, 1.25f); + ImGui::SliderFloat("RCAS", &mSettings.view.taa.sharpness, 0.0f, 1.0f); + mSettings.view.taa.boxClipping = (TemporalAntiAliasingOptions::BoxClipping)boxClipping; + mSettings.view.taa.boxType = (TemporalAntiAliasingOptions::BoxType)boxType; + mSettings.view.taa.jitterPattern = (TemporalAntiAliasingOptions::JitterPattern)jitterSequence; + } + if (ImGui::CollapsingHeader("SSAO Options")) { auto& ssao = mSettings.view.ssao; @@ -1014,6 +1040,7 @@ void ViewerGui::updateUserInterface() { ImGui::Checkbox("Enabled##dofEnabled", &mSettings.view.dof.enabled); ImGui::SliderFloat("Focus distance", &mSettings.viewer.cameraFocusDistance, 0.0f, 30.0f); ImGui::SliderFloat("Blur scale", &mSettings.view.dof.cocScale, 0.1f, 10.0f); + ImGui::SliderFloat("CoC aspect-ratio", &mSettings.view.dof.cocAspectRatio, 0.25f, 4.0f); ImGui::SliderInt("Ring count", &dofRingCount, 1, 17); ImGui::SliderInt("Max CoC", &dofMaxCoC, 1, 32); ImGui::Checkbox("Native Resolution", &mSettings.view.dof.nativeResolution); @@ -1045,7 +1072,7 @@ void ViewerGui::updateUserInterface() { std::vector names; names.reserve(cameraCount + 1); - names.push_back("Free camera"); + names.emplace_back("Free camera"); int c = 0; for (size_t i = 0; i < cameraCount; i++) { const char* n = mAsset->getName(cameras[i]); @@ -1060,20 +1087,20 @@ void ViewerGui::updateUserInterface() { std::vector cstrings; cstrings.reserve(names.size()); - for (size_t i = 0; i < names.size(); i++) { - cstrings.push_back(names[i].c_str()); + for (const auto & name : names) { + cstrings.push_back(name.c_str()); } ImGui::ListBox("Cameras", &mCurrentCamera, cstrings.data(), cstrings.size()); } - StereoscopicOptions stereoOptions = mView->getStereoscopicOptions(); - ImGui::Checkbox("Instanced stereo", &stereoOptions.enabled); - if (stereoOptions.enabled) { - ImGui::SliderFloat("Ocular distance", &mOcularDistance, 0.0f, 10.0f); + ImGui::Checkbox("Instanced stereo", &mSettings.view.stereoscopicOptions.enabled); + ImGui::SliderFloat( + "Ocular distance", &mSettings.viewer.cameraEyeOcularDistance, 0.0f, 1.0f); - } - mView->setStereoscopicOptions(stereoOptions); + float toeInDegrees = mSettings.viewer.cameraEyeToeIn / f::PI * 180.0f; + ImGui::SliderFloat("Toe in", &toeInDegrees, 0.0f, 30.0, "%.3f°"); + mSettings.viewer.cameraEyeToeIn = toeInDegrees / 180.0f * f::PI; ImGui::Unindent(); } @@ -1083,8 +1110,16 @@ void ViewerGui::updateUserInterface() { // At this point, all View settings have been modified, // so we can now push them into the Filament View. applySettings(mEngine, mSettings.view, mView); + + auto lights = utils::FixedCapacityVector::with_capacity(mScene->getEntityCount()); + mScene->forEach([&](utils::Entity entity) { + if (lm.hasComponent(entity)) { + lights.push_back(entity); + } + }); + applySettings(mEngine, mSettings.lighting, mIndirectLight, mSunlight, - lm.getEntities(), lm.getComponentCount(), &lm, mScene, mView); + lights.data(), lights.size(), &lm, mScene, mView); // TODO(prideout): add support for hierarchy, animation and variant selection in remote mode. To // support these features, we will need to send a message (list of strings) from DebugServer to diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 00a17efee6f..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}" @@ -222,6 +231,12 @@ function(add_demo NAME) target_link_libraries(${NAME} PRIVATE sample-resources filamentapp) target_compile_options(${NAME} PRIVATE ${COMPILER_FLAGS}) set_target_properties(${NAME} PROPERTIES FOLDER Samples) + + # This is needed after XCode 15.3 + if (APPLE) + set_target_properties(${NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) + set_target_properties(${NAME} PROPERTIES INSTALL_RPATH /usr/local/lib) + endif() endfunction() if (NOT ANDROID) diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 1eb1c09542d..80a06298e71 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -41,9 +41,12 @@ #include +#include + #include #include +#include #include #include @@ -53,9 +56,12 @@ #include #include +#include + #include #include #include +#include #include #include "generated/resources/gltf_demo.h" @@ -178,10 +184,13 @@ static void printUsage(char* name) { " Set the camera mode: orbit (default) or flight\n" " Flight mode uses the following controls:\n" " Click and drag the mouse to pan the camera\n" - " Use the scroll weel to adjust movement speed\n" + " Use the scroll wheel to adjust movement speed\n" " W / S: forward / backward\n" " A / D: left / right\n" " E / Q: up / down\n\n" + " --eyes=, -y \n" + " Sets the number of stereoscopic eyes (default: 2) when stereoscopic rendering is\n" + " enabled.\n\n" " --split-view, -v\n" " Splits the window into 4 views\n\n" " --vulkan-gpu-hint=, -g\n" @@ -213,6 +222,7 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { { "ubershader", no_argument, nullptr, 'u' }, { "actual-size", no_argument, nullptr, 's' }, { "camera", required_argument, nullptr, 'c' }, + { "eyes", required_argument, nullptr, 'y' }, { "recompute-aabb", no_argument, nullptr, 'r' }, { "settings", required_argument, nullptr, 't' }, { "split-view", no_argument, nullptr, 'v' }, @@ -259,6 +269,19 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { std::cerr << "Unrecognized camera mode. Must be 'flight'|'orbit'.\n"; } break; + case 'y': { + int eyeCount = 0; + try { + eyeCount = std::stoi(arg); + } catch (std::invalid_argument &e) { } + if (eyeCount >= 1 && eyeCount <= CONFIG_MAX_STEREOSCOPIC_EYES) { + app->config.stereoscopicEyeCount = eyeCount; + } else { + std::cerr << "Eye count must be between 1 and CONFIG_MAX_STEREOSCOPIC_EYES (" + << (int) CONFIG_MAX_STEREOSCOPIC_EYES << ") (inclusive).\n"; + } + break; + } case 'e': app->config.headless = true; break; @@ -485,6 +508,57 @@ static void onClick(App& app, View* view, ImVec2 pos) { }); } +static utils::Path getPathForAsset(std::string_view string) { + utils::Path filename{ string }; + if (!filename.exists()) { + std::cerr << "file " << filename << " not found!" << std::endl; + return {}; + } + if (filename.isDirectory()) { + auto files = filename.listContents(); + for (const auto& file: files) { + if (file.getExtension() == "gltf" || file.getExtension() == "glb") { + filename = file; + break; + } + } + if (filename.isDirectory()) { + std::cerr << "no glTF file found in " << filename << std::endl; + return {}; + } + } + return filename; +} + + +static bool checkAsset(const utils::Path& filename) { + // Peek at the file size to allow pre-allocation. + long const contentSize = static_cast(getFileSize(filename.c_str())); + if (contentSize <= 0) { + std::cerr << "Unable to open " << filename << std::endl; + return false; + } + + // Consume the glTF file. + std::ifstream in(filename.c_str(), std::ifstream::binary | std::ifstream::in); + std::vector buffer(static_cast(contentSize)); + if (!in.read((char*) buffer.data(), contentSize)) { + std::cerr << "Unable to read " << filename << std::endl; + return false; + } + + // Parse the glTF file and create Filament entities. + cgltf_options options{}; + cgltf_data* sourceAsset; + cgltf_result result = cgltf_parse(&options, buffer.data(), contentSize, &sourceAsset); + if (result != cgltf_result_success) { + slog.e << "Unable to parse glTF file." << io::endl; + return false; + } + return true; +}; + + int main(int argc, char** argv) { App app; @@ -496,24 +570,10 @@ int main(int argc, char** argv) { utils::Path filename; int const num_args = argc - optionIndex; if (num_args >= 1) { - filename = argv[optionIndex]; - if (!filename.exists()) { - std::cerr << "file " << filename << " not found!" << std::endl; + filename = getPathForAsset(argv[optionIndex]); + if (filename.isEmpty()) { return 1; } - if (filename.isDirectory()) { - auto files = filename.listContents(); - for (const auto& file : files) { - if (file.getExtension() == "gltf" || file.getExtension() == "glb") { - filename = file; - break; - } - } - if (filename.isDirectory()) { - std::cerr << "no glTF file found in " << filename << std::endl; - return 1; - } - } } auto loadAsset = [&app](const utils::Path& filename) { @@ -534,14 +594,49 @@ int main(int argc, char** argv) { // Parse the glTF file and create Filament entities. app.asset = app.assetLoader->createAsset(buffer.data(), buffer.size()); - app.instance = app.asset->getInstance(); - buffer.clear(); - buffer.shrink_to_fit(); - if (!app.asset) { std::cerr << "Unable to parse " << filename << std::endl; exit(1); } + + // pre-compile all material variants + std::set materials; + RenderableManager const& rcm = app.engine->getRenderableManager(); + Slice const renderables{ + app.asset->getRenderableEntities(), app.asset->getRenderableEntityCount() }; + for (Entity const e: renderables) { + auto ri = rcm.getInstance(e); + size_t const c = rcm.getPrimitiveCount(ri); + for (size_t i = 0; i < c; i++) { + MaterialInstance* const mi = rcm.getMaterialInstanceAt(ri, i); + Material* ma = const_cast(mi->getMaterial()); + materials.insert(ma); + } + } + for (Material* ma : materials) { + // Don't attempt to precompile shaders on WebGL. + // Chrome already suffers from slow shader compilation: + // https://github.com/google/filament/issues/6615 + // Precompiling shaders exacerbates the problem. +#if !defined(__EMSCRIPTEN__) + // First compile high priority variants + ma->compile(Material::CompilerPriorityQueue::HIGH, + UserVariantFilterBit::DIRECTIONAL_LIGHTING | + UserVariantFilterBit::DYNAMIC_LIGHTING | + UserVariantFilterBit::SHADOW_RECEIVER); + + // and then, everything else at low priority, except STE, which is very uncommon. + ma->compile(Material::CompilerPriorityQueue::LOW, + UserVariantFilterBit::FOG | + UserVariantFilterBit::SKINNING | + UserVariantFilterBit::SSR | + UserVariantFilterBit::VSM); +#endif + } + + app.instance = app.asset->getInstance(); + buffer.clear(); + buffer.shrink_to_fit(); }; auto loadResources = [&app] (const utils::Path& filename) { @@ -559,6 +654,8 @@ int main(int argc, char** argv) { app.resourceLoader->addTextureProvider("image/png", app.stbDecoder); app.resourceLoader->addTextureProvider("image/jpeg", app.stbDecoder); app.resourceLoader->addTextureProvider("image/ktx2", app.ktxDecoder); + } else { + app.resourceLoader->setConfiguration(configuration); } if (!app.resourceLoader->asyncBeginLoad(app.asset)) { @@ -776,6 +873,39 @@ int main(int argc, char** argv) { debugDirectionalShadowmap); } + ImGui::Checkbox("Display Shadow Texture", + debug.getPropertyAddress("d.shadowmap.display_shadow_texture")); + if (*debug.getPropertyAddress("d.shadowmap.display_shadow_texture")) { + int layerCount; + int levelCount; + debug.getProperty("d.shadowmap.display_shadow_texture_layer_count", &layerCount); + debug.getProperty("d.shadowmap.display_shadow_texture_level_count", &levelCount); + ImGui::Indent(); + ImGui::SliderFloat("scale", debug.getPropertyAddress( + "d.shadowmap.display_shadow_texture_scale"), 0.0f, 8.0f); + ImGui::SliderFloat("contrast", debug.getPropertyAddress( + "d.shadowmap.display_shadow_texture_power"), 0.0f, 2.0f); + ImGui::SliderInt("layer", debug.getPropertyAddress( + "d.shadowmap.display_shadow_texture_layer"), 0, layerCount - 1); + ImGui::SliderInt("level", debug.getPropertyAddress( + "d.shadowmap.display_shadow_texture_level"), 0, levelCount - 1); + ImGui::SliderInt("channel", debug.getPropertyAddress( + "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", + &debugFroxelVisualization)) { + ImGui::Checkbox("Froxel Visualization", &debugFroxelVisualization); + debug.setProperty("d.lighting.debug_froxel_visualization", + debugFroxelVisualization); + } + auto dataSource = debug.getDataSource("d.view.frame_info"); if (dataSource.data) { ImGuiExt::PlotLinesSeries("FrameInfo", 6, @@ -935,28 +1065,11 @@ int main(int argc, char** argv) { camera->setScaling({1.0 / aspectRatio, 1.0}); } - if (view->getStereoscopicOptions().enabled) { - Camera& c = view->getCamera(); - auto od = app.viewer->getOcularDistance(); - // Eye 0 is always rendered to the left side of the screen; Eye 1, the right side. - // For testing, we want to render a side-by-side layout so users can view with - // "cross-eyed" stereo. - // For cross-eyed stereo, Eye 0 is really the RIGHT eye, while Eye 1 is the LEFT eye. - const mat4 rightEye = mat4::translation(double3{ od, 0.0, 0.0}); // right eye - const mat4 leftEye = mat4::translation(double3{-od, 0.0, 0.0}); // left eye - c.setEyeModelMatrix(0, rightEye); - c.setEyeModelMatrix(1, leftEye); - mat4 projections[2]; - // Use an aspect ratio of 1.0. The viewport will be taken into account in - // FilamentApp.cpp. - projections[0] = mat4::perspective(70.0, 1.0, .1, 10.0); - projections[1] = mat4::perspective(70.0, 1.0, .1, 10.0); - c.setCustomEyeProjection(projections, 2, projections[0], .1, 10.0); - // FIXME: the aspect ratio will be incorrect until configureCamerasForWindow is - // triggered, which will happen the next time the window is resized. - } else { - view->getCamera().setEyeModelMatrix(0, {}); - view->getCamera().setEyeModelMatrix(1, {}); + static bool stereoscopicEnabled = false; + if (stereoscopicEnabled != view->getStereoscopicOptions().enabled) { + // Stereo was turned on/off. + FilamentApp::get().reconfigureCameras(); + stereoscopicEnabled = view->getStereoscopicOptions().enabled; } app.scene.groundMaterial->setDefaultParameter( @@ -1013,14 +1126,19 @@ int main(int argc, char** argv) { filamentApp.animate(animate); filamentApp.resize(resize); - filamentApp.setDropHandler([&] (std::string_view path) { - app.resourceLoader->asyncCancelLoad(); - app.resourceLoader->evictResourceData(); - app.viewer->removeAsset(); - app.assetLoader->destroyAsset(app.asset); - loadAsset(path); - loadResources(path); - app.viewer->setAsset(app.asset, app.instance); + filamentApp.setDropHandler([&](std::string_view path) { + utils::Path const filename = getPathForAsset(path); + if (!filename.isEmpty()) { + if (checkAsset(filename)) { + app.resourceLoader->asyncCancelLoad(); + app.resourceLoader->evictResourceData(); + app.viewer->removeAsset(); + app.assetLoader->destroyAsset(app.asset); + loadAsset(filename); + loadResources(filename); + app.viewer->setAsset(app.asset, app.instance); + } + } }); filamentApp.run(app.config, setup, cleanup, gui, preRender, postRender); diff --git a/samples/hellotriangle.cpp b/samples/hellotriangle.cpp index c6469fc1b37..4477429dd63 100644 --- a/samples/hellotriangle.cpp +++ b/samples/hellotriangle.cpp @@ -122,6 +122,7 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { int main(int argc, char** argv) { App app{}; app.config.title = "hellotriangle"; + app.config.featureLevel = backend::FeatureLevel::FEATURE_LEVEL_0; handleCommandLineArguments(argc, argv, &app); auto setup = [&app](Engine* engine, View* view, Scene* scene) { diff --git a/samples/materials/bakedColor.mat b/samples/materials/bakedColor.mat index ee10a3035ea..c78cef16ac6 100644 --- a/samples/materials/bakedColor.mat +++ b/samples/materials/bakedColor.mat @@ -4,7 +4,8 @@ material { color ], shadingModel : unlit, - culling : none + culling : none, + featureLevel : 0 } fragment { diff --git a/shaders/src/common_getters.glsl b/shaders/src/common_getters.glsl index 54ee7be33dd..a2bbebe4f8b 100644 --- a/shaders/src/common_getters.glsl +++ b/shaders/src/common_getters.glsl @@ -24,9 +24,13 @@ highp mat4 getViewFromClipMatrix() { /** @public-api */ highp mat4 getClipFromWorldMatrix() { -#if defined(VARIANT_HAS_INSTANCED_STEREO) - int eye = instance_index % CONFIG_STEREOSCOPIC_EYES; +#if defined(VARIANT_HAS_STEREO) +#if defined(FILAMENT_STEREO_INSTANCED) + int eye = instance_index % CONFIG_STEREO_EYE_COUNT; return frameUniforms.clipFromWorldMatrix[eye]; +#elif defined(FILAMENT_STEREO_MULTIVIEW) + return frameUniforms.clipFromWorldMatrix[gl_ViewID_OVR]; +#endif #else return frameUniforms.clipFromWorldMatrix[0]; #endif diff --git a/shaders/src/common_math.glsl b/shaders/src/common_math.glsl index 98b205f65dc..28d04685d16 100644 --- a/shaders/src/common_math.glsl +++ b/shaders/src/common_math.glsl @@ -19,7 +19,6 @@ #endif #define saturate(x) clamp(x, 0.0, 1.0) -#define atan2(x, y) atan(y, x) //------------------------------------------------------------------------------ // Scalar operations diff --git a/shaders/src/depth_main.fs b/shaders/src/depth_main.fs index fa28309a88c..e50d7620f17 100644 --- a/shaders/src/depth_main.fs +++ b/shaders/src/depth_main.fs @@ -57,10 +57,10 @@ void main() { fragColor.xy = computeDepthMomentsVSM(depth); fragColor.zw = computeDepthMomentsVSM(-1.0 / depth); // requires at least RGBA16F #elif defined(VARIANT_HAS_PICKING) -#if MATERIAL_FEATURE_LEVEL == 0 - outPicking.a = float((object_uniforms_objectId / 65536) % 256) / 255.0; - outPicking.b = float((object_uniforms_objectId / 256) % 256) / 255.0; - outPicking.g = float( object_uniforms_objectId % 256) / 255.0; +#if FILAMENT_EFFECTIVE_VERSION == 100 + outPicking.a = mod(float(object_uniforms_objectId / 65536), 256.0) / 255.0; + outPicking.b = mod(float(object_uniforms_objectId / 256), 256.0) / 255.0; + outPicking.g = mod(float(object_uniforms_objectId) , 256.0) / 255.0; outPicking.r = vertex_position.z / vertex_position.w; #else outPicking.x = intBitsToFloat(object_uniforms_objectId); diff --git a/shaders/src/light_punctual.fs b/shaders/src/light_punctual.fs index 14c6f9fbcbc..0db8a4ebf23 100644 --- a/shaders/src/light_punctual.fs +++ b/shaders/src/light_punctual.fs @@ -242,4 +242,29 @@ void evaluatePunctualLights(const MaterialInputs material, color.rgb += surfaceShading(pixel, light, visibility); #endif } + + if (CONFIG_DEBUG_FROXEL_VISUALIZATION) { + if (froxel.count > 0u) { + const vec3 debugColors[17] = vec3[]( + vec3(0.0, 0.0, 0.0), // black + vec3(0.0, 0.0, 0.1647), // darkest blue + vec3(0.0, 0.0, 0.3647), // darker blue + vec3(0.0, 0.0, 0.6647), // dark blue + vec3(0.0, 0.0, 0.9647), // blue + vec3(0.0, 0.9255, 0.9255), // cyan + vec3(0.0, 0.5647, 0.0), // dark green + vec3(0.0, 0.7843, 0.0), // green + vec3(1.0, 1.0, 0.0), // yellow + vec3(0.90588, 0.75294, 0.0), // yellow-orange + vec3(1.0, 0.5647, 0.0), // orange + vec3(1.0, 0.0, 0.0), // bright red + vec3(0.8392, 0.0, 0.0), // red + vec3(1.0, 0.0, 1.0), // magenta + vec3(0.6, 0.3333, 0.7882), // purple + vec3(1.0, 1.0, 1.0), // white + vec3(1.0, 1.0, 1.0) // white + ); + color = mix(color, debugColors[clamp(froxel.count, 0u, 16u)], 0.8); + } + } } diff --git a/shaders/src/main.fs b/shaders/src/main.fs index 375960515ea..1bc74372c13 100644 --- a/shaders/src/main.fs +++ b/shaders/src/main.fs @@ -6,17 +6,19 @@ layout(location = 0) out vec4 fragColor; #if defined(MATERIAL_HAS_POST_LIGHTING_COLOR) void blendPostLightingColor(const MaterialInputs material, inout vec4 color) { + vec4 blend = color; #if defined(POST_LIGHTING_BLEND_MODE_OPAQUE) - color = material.postLightingColor; + blend = material.postLightingColor; #elif defined(POST_LIGHTING_BLEND_MODE_TRANSPARENT) - color = material.postLightingColor + color * (1.0 - material.postLightingColor.a); + blend = material.postLightingColor + color * (1.0 - material.postLightingColor.a); #elif defined(POST_LIGHTING_BLEND_MODE_ADD) - color += material.postLightingColor; + blend += material.postLightingColor; #elif defined(POST_LIGHTING_BLEND_MODE_MULTIPLY) - color *= material.postLightingColor; + blend *= material.postLightingColor; #elif defined(POST_LIGHTING_BLEND_MODE_SCREEN) - color += material.postLightingColor * (1.0 - color); + blend += material.postLightingColor * (1.0 - color); #endif + color = mix(color, blend, material.postLightingMixFactor); } #endif diff --git a/shaders/src/main.vs b/shaders/src/main.vs index a52bc527d1d..bbb9230fc8d 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -23,13 +23,12 @@ void main() { logical_instance_index = instance_index; #endif -#if defined(VARIANT_HAS_INSTANCED_STEREO) +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) #if !defined(FILAMENT_HAS_FEATURE_INSTANCING) #error Instanced stereo not supported at this feature level #endif - // The lowest bit of the instance index represents the eye. - // This logic must be updated if CONFIG_STEREOSCOPIC_EYES changes - logical_instance_index = instance_index >> 1; + // Calculate the logical instance index, which is the instance index within a single eye. + logical_instance_index = instance_index / CONFIG_STEREO_EYE_COUNT; #endif initObjectUniforms(); @@ -216,23 +215,27 @@ void main() { // this must happen before we compensate for vulkan below vertex_position = position; -#if defined(VARIANT_HAS_INSTANCED_STEREO) - // This logic must be updated if CONFIG_STEREOSCOPIC_EYES changes +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) // We're transforming a vertex whose x coordinate is within the range (-w to w). - // To move it to the correct half of the viewport, we need to modify the x coordinate: - // Eye 0 (left half): (-w to 0) - // Eye 1 (right half): ( 0 to w) + // To move it to the correct portion of the viewport, we need to modify the x coordinate. // It's important to do this after computing vertex_position. - int eyeIndex = instance_index % 2; - float eyeShift = float(eyeIndex) * 2.0f - 1.0f; // eye 0: -1.0, eye 1: 1.0 - position.x = position.x * 0.5f + (position.w * 0.5 * eyeShift); - - // A fragment is clipped when gl_ClipDistance is negative (outside the clip plane). So, - // Eye 0 should have a positive value when x is < 0 - // -position.x - // Eye 1 should have a positive value when x is > 0 - // position.x - FILAMENT_CLIPDISTANCE[0] = position.x * eyeShift; + int eyeIndex = instance_index % CONFIG_STEREO_EYE_COUNT; + + float ndcViewportWidth = 2.0 / float(CONFIG_STEREO_EYE_COUNT); // the width of ndc space is 2 + float eyeZeroMidpoint = -1.0f + ndcViewportWidth / 2.0; + + float transform = eyeZeroMidpoint + ndcViewportWidth * float(eyeIndex); + position.x *= 1.0 / float(CONFIG_STEREO_EYE_COUNT); + position.x += transform * position.w; + + // A fragment is clipped when gl_ClipDistance is negative (outside the clip plane). + + float leftClip = position.x + + (1.0 - ndcViewportWidth * float(eyeIndex)) * position.w; + float rightClip = position.x + + (1.0 - ndcViewportWidth * float(eyeIndex + 1)) * position.w; + FILAMENT_CLIPDISTANCE[0] = leftClip; + FILAMENT_CLIPDISTANCE[1] = -rightClip; #endif #if defined(TARGET_VULKAN_ENVIRONMENT) @@ -250,7 +253,7 @@ void main() { // some PowerVR drivers crash when gl_Position is written more than once gl_Position = position; -#if defined(VARIANT_HAS_INSTANCED_STEREO) +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) // Fragment shaders filter out the stereo variant, so we need to set instance_index here. instance_index = logical_instance_index; #endif diff --git a/shaders/src/material_inputs.fs b/shaders/src/material_inputs.fs index 3844df9f2bc..53d4ade1ecf 100644 --- a/shaders/src/material_inputs.fs +++ b/shaders/src/material_inputs.fs @@ -66,6 +66,7 @@ struct MaterialInputs { #if defined(MATERIAL_HAS_POST_LIGHTING_COLOR) vec4 postLightingColor; + float postLightingMixFactor; #endif #if !defined(SHADING_MODEL_CLOTH) && !defined(SHADING_MODEL_SUBSURFACE) && !defined(SHADING_MODEL_UNLIT) @@ -153,6 +154,7 @@ void initMaterial(out MaterialInputs material) { #if defined(MATERIAL_HAS_POST_LIGHTING_COLOR) material.postLightingColor = vec4(0.0); + material.postLightingMixFactor = 1.0; #endif #if !defined(SHADING_MODEL_CLOTH) && !defined(SHADING_MODEL_SUBSURFACE) && !defined(SHADING_MODEL_UNLIT) diff --git a/shaders/src/post_process.vs b/shaders/src/post_process.vs index 602b3ee1a59..c9b80e244ee 100644 --- a/shaders/src/post_process.vs +++ b/shaders/src/post_process.vs @@ -5,22 +5,27 @@ void main() { inputs.normalizedUV = position.xy * 0.5 + 0.5; - vec4 position = getPosition(); + inputs.position = getPosition(); - // GL convention to inverted DX convention - position.z = position.z * -0.5 + 0.5; + // Invoke user code + postProcessVertex(inputs); + + // (vertex domain) GL convention to inverted DX convention + inputs.position.z = inputs.position.z * -0.5 + 0.5; + +#if defined(TARGET_VULKAN_ENVIRONMENT) + // In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up. + inputs.position.y = -inputs.position.y; +#endif // Adjust clip-space #if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT) // This is not needed in Vulkan or Metal because clipControl is always (1, 0) - // (We don't use a dot() here because it workaround a spirv-opt optimization that in turn + // (We don't use a dot() here because it works around a spirv-opt optimization that in turn // causes a crash on PowerVR, see #5118) - position.z = position.z * frameUniforms.clipControl.x + position.w * frameUniforms.clipControl.y; + inputs.position.z = inputs.position.z * frameUniforms.clipControl.x + inputs.position.w * frameUniforms.clipControl.y; #endif - // Invoke user code - postProcessVertex(inputs); - // Handle user-defined interpolated attributes #if defined(VARIABLE_CUSTOM0) VARIABLE_CUSTOM_AT0 = inputs.VARIABLE_CUSTOM0; @@ -36,5 +41,5 @@ void main() { #endif // some PowerVR drivers crash when gl_Position is written more than once - gl_Position = position; + gl_Position = inputs.position; } diff --git a/shaders/src/post_process_getters.vs b/shaders/src/post_process_getters.vs index 47cd2544bef..8b641e5bdfc 100644 --- a/shaders/src/post_process_getters.vs +++ b/shaders/src/post_process_getters.vs @@ -1,11 +1,4 @@ /** @public-api */ vec4 getPosition() { - vec4 pos = position; - -// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up. -#if defined(TARGET_VULKAN_ENVIRONMENT) - pos.y = -pos.y; -#endif - - return pos; + return position; } diff --git a/shaders/src/post_process_inputs.vs b/shaders/src/post_process_inputs.vs index 1bbb1256a0b..eb2af8ab084 100644 --- a/shaders/src/post_process_inputs.vs +++ b/shaders/src/post_process_inputs.vs @@ -1,10 +1,13 @@ -LAYOUT_LOCATION(LOCATION_POSITION) in vec4 position; +LAYOUT_LOCATION(LOCATION_POSITION) ATTRIBUTE vec4 position; struct PostProcessVertexInputs { // We provide normalized texture coordinates to custom vertex shaders. vec2 normalizedUV; + // vertex position, can be modified by the user code + vec4 position; + #ifdef VARIABLE_CUSTOM0 vec4 VARIABLE_CUSTOM0; #endif diff --git a/shaders/src/shading_lit.fs b/shaders/src/shading_lit.fs index a0d7081df86..bf80ea57cd9 100644 --- a/shaders/src/shading_lit.fs +++ b/shaders/src/shading_lit.fs @@ -37,8 +37,11 @@ float normalFiltering(float perceptualRoughness, const vec3 worldNormal) { vec3 du = dFdx(worldNormal); vec3 dv = dFdy(worldNormal); - float variance = materialParams._specularAntiAliasingVariance * (dot(du, du) + dot(dv, dv)); + // specular AA factor to correct for resolution scaling (DSR and TAAx4) + du *= frameUniforms.derivativesScale.x; + dv *= frameUniforms.derivativesScale.y; + float variance = materialParams._specularAntiAliasingVariance * (dot(du, du) + dot(dv, dv)); float roughness = perceptualRoughnessToRoughness(perceptualRoughness); float kernelRoughness = min(2.0 * variance, materialParams._specularAntiAliasingThreshold); float squareRoughness = saturate(roughness * roughness + kernelRoughness); diff --git a/shaders/src/varyings.glsl b/shaders/src/varyings.glsl index 40eb3723504..a0478838ded 100644 --- a/shaders/src/varyings.glsl +++ b/shaders/src/varyings.glsl @@ -34,16 +34,16 @@ LAYOUT_LOCATION(11) VARYING highp vec4 vertex_lightSpacePosition; // Note that fragColor is an output and is not declared here; see main.fs and depth_main.fs -#if defined(VARIANT_HAS_INSTANCED_STEREO) +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) #if defined(GL_ES) && defined(FILAMENT_GLSLANG) // On ES, gl_ClipDistance is not a built-in, so we have to rely on EXT_clip_cull_distance // However, this extension is not supported by glslang, so we instead write to // filament_gl_ClipDistance, which will get decorated at the SPIR-V stage to refer to the built-in. // The location here does not matter, so long as it doesn't conflict with others. -LAYOUT_LOCATION(100) out float filament_gl_ClipDistance[1]; +LAYOUT_LOCATION(100) out float filament_gl_ClipDistance[2]; #define FILAMENT_CLIPDISTANCE filament_gl_ClipDistance #else // If we're on Desktop GL (or not running shaders through glslang), we're free to use gl_ClipDistance #define FILAMENT_CLIPDISTANCE gl_ClipDistance #endif // GL_ES && FILAMENT_GLSLANG -#endif // VARIANT_HAS_INSTANCED_STEREO +#endif // VARIANT_HAS_STEREO diff --git a/third_party/mikktspace/CMakeLists.txt b/third_party/mikktspace/CMakeLists.txt index 120013568da..36959aebf66 100644 --- a/third_party/mikktspace/CMakeLists.txt +++ b/third_party/mikktspace/CMakeLists.txt @@ -4,23 +4,12 @@ project(mikktspace) set(TARGET mikktspace) set(PUBLIC_HDR_DIR include) -# ================================================================================================== -# Sources and headers -# ================================================================================================== -set(PUBLIC_HDRS - ${PUBLIC_HDR_DIR}/mikktspace/mikktspace.h -) - -set(SRCS - src/mikktspace.c -) - # ================================================================================================== # Target definitions # ================================================================================================== include_directories(${PUBLIC_HDR_DIR}) -add_library(${TARGET} ${PUBLIC_HDRS} ${PRIVATE_HDRS} ${SRCS}) +add_library(${TARGET} STATIC ${PUBLIC_HDR_DIR}/mikktspace/mikktspace.h src/mikktspace.c) target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) set_target_properties(${TARGET} PROPERTIES FOLDER Libs) @@ -30,7 +19,7 @@ set_target_properties(${TARGET} PROPERTIES FOLDER Libs) if (MSVC) target_compile_options(${TARGET} PRIVATE /fp:fast) else() - target_compile_options(${TARGET} PRIVATE -ffast-math) + target_compile_options(${TARGET} PRIVATE $<$:-ffast-math -fno-finite-math-only>) endif() # ================================================================================================== diff --git a/third_party/spirv-cross/spirv_glsl.cpp b/third_party/spirv-cross/spirv_glsl.cpp index 0d63d35f8f2..e0b39f95bb7 100644 --- a/third_party/spirv-cross/spirv_glsl.cpp +++ b/third_party/spirv-cross/spirv_glsl.cpp @@ -14977,7 +14977,12 @@ string CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, const Bitset { auto &execution = get_entry_point(); - if (flags.get(DecorationRelaxedPrecision)) + if (type.basetype == SPIRType::UInt && is_legacy_es()) + { + // HACK: This is a bool. See comment in type_to_glsl(). + qual += "lowp "; + } + else if (flags.get(DecorationRelaxedPrecision)) { bool implied_fmediump = type.basetype == SPIRType::Float && options.fragment.default_float_precision == Options::Mediump && @@ -15503,7 +15508,11 @@ string CompilerGLSL::type_to_glsl(const SPIRType &type, uint32_t id) if (type.basetype == SPIRType::UInt && is_legacy()) { if (options.es) - SPIRV_CROSS_THROW("Unsigned integers are not supported on legacy ESSL."); + // HACK: spirv-cross changes bools into uints and generates code which compares them to + // zero. Input code will have already been validated as not to have contained any uints, + // so any remaining uints must in fact be bools. However, simply returning "bool" here + // will result in invalid code. Instead, return an int. + return backend.basic_int_type; else require_extension_internal("GL_EXT_gpu_shader4"); } diff --git a/third_party/spirv-tools/external/spirv-headers/.gitattributes b/third_party/spirv-headers/.gitattributes similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/.gitattributes rename to third_party/spirv-headers/.gitattributes diff --git a/third_party/spirv-tools/external/spirv-headers/.gitignore b/third_party/spirv-headers/.gitignore similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/.gitignore rename to third_party/spirv-headers/.gitignore diff --git a/third_party/spirv-tools/external/spirv-headers/BUILD.bazel b/third_party/spirv-headers/BUILD.bazel similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/BUILD.bazel rename to third_party/spirv-headers/BUILD.bazel diff --git a/third_party/spirv-tools/external/spirv-headers/BUILD.gn b/third_party/spirv-headers/BUILD.gn similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/BUILD.gn rename to third_party/spirv-headers/BUILD.gn diff --git a/third_party/spirv-tools/external/spirv-headers/CMakeLists.txt b/third_party/spirv-headers/CMakeLists.txt similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/CMakeLists.txt rename to third_party/spirv-headers/CMakeLists.txt diff --git a/third_party/spirv-tools/external/spirv-headers/CODE_OF_CONDUCT.md b/third_party/spirv-headers/CODE_OF_CONDUCT.md similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/CODE_OF_CONDUCT.md rename to third_party/spirv-headers/CODE_OF_CONDUCT.md diff --git a/third_party/spirv-headers/FILAMENT_README.md b/third_party/spirv-headers/FILAMENT_README.md new file mode 100644 index 00000000000..c3eed55a2ef --- /dev/null +++ b/third_party/spirv-headers/FILAMENT_README.md @@ -0,0 +1,5 @@ +This project is pulled in as a dependency for spirv-tools. The Vulkan +backend also has a dependency on this project. This project should not +be updated directly, instead, please see +/third_party/spirv-tools/FILAMENT_README.md for instructions for +update. diff --git a/third_party/spirv-tools/external/spirv-headers/LICENSE b/third_party/spirv-headers/LICENSE similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/LICENSE rename to third_party/spirv-headers/LICENSE diff --git a/third_party/spirv-tools/external/spirv-headers/README.md b/third_party/spirv-headers/README.md similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/README.md rename to third_party/spirv-headers/README.md diff --git a/third_party/spirv-tools/external/spirv-headers/SPIRV-Headers.pc.in b/third_party/spirv-headers/SPIRV-Headers.pc.in similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/SPIRV-Headers.pc.in rename to third_party/spirv-headers/SPIRV-Headers.pc.in diff --git a/third_party/spirv-tools/external/spirv-headers/WORKSPACE b/third_party/spirv-headers/WORKSPACE similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/WORKSPACE rename to third_party/spirv-headers/WORKSPACE diff --git a/third_party/spirv-tools/external/spirv-headers/cmake/Config.cmake.in b/third_party/spirv-headers/cmake/Config.cmake.in similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/cmake/Config.cmake.in rename to third_party/spirv-headers/cmake/Config.cmake.in diff --git a/third_party/spirv-tools/external/spirv-headers/example/CMakeLists.txt b/third_party/spirv-headers/example/CMakeLists.txt similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/example/CMakeLists.txt rename to third_party/spirv-headers/example/CMakeLists.txt diff --git a/third_party/spirv-tools/external/spirv-headers/example/example.cpp b/third_party/spirv-headers/example/example.cpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/example/example.cpp rename to third_party/spirv-headers/example/example.cpp diff --git a/third_party/spirv-headers/filament-specific-changes.patch b/third_party/spirv-headers/filament-specific-changes.patch new file mode 100644 index 00000000000..b9ddb551ae4 --- /dev/null +++ b/third_party/spirv-headers/filament-specific-changes.patch @@ -0,0 +1,27 @@ +diff --git a/third_party/spirv-headers/CMakeLists.txt b/third_party/spirv-headers/CMakeLists.txt +index 9cfba73af..2b8bb5e59 100644 +--- a/third_party/spirv-headers/CMakeLists.txt ++++ b/third_party/spirv-headers/CMakeLists.txt +@@ -28,7 +28,7 @@ + # The SPIR-V headers from the SPIR-V Registry + # https://www.khronos.org/registry/spir-v/ + # +-cmake_minimum_required(VERSION 3.0) ++cmake_minimum_required(VERSION 3.19) + project(SPIRV-Headers VERSION 1.5.5) + + # There are two ways to use this project. +@@ -44,11 +44,9 @@ project(SPIRV-Headers VERSION 1.5.5) + # 2. cmake .. + # 3. cmake --build . --target install + +-option(SPIRV_HEADERS_SKIP_EXAMPLES "Skip building examples" +- ${SPIRV_HEADERS_SKIP_EXAMPLES}) ++option(SPIRV_HEADERS_SKIP_EXAMPLES "Skip building examples" ON) + +-option(SPIRV_HEADERS_SKIP_INSTALL "Skip install" +- ${SPIRV_HEADERS_SKIP_INSTALL}) ++option(SPIRV_HEADERS_SKIP_INSTALL "Skip install" ON) + + if(NOT ${SPIRV_HEADERS_SKIP_EXAMPLES}) + set(SPIRV_HEADERS_ENABLE_EXAMPLES ON) diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/GLSL.std.450.h b/third_party/spirv-headers/include/spirv/1.0/GLSL.std.450.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/GLSL.std.450.h rename to third_party/spirv-headers/include/spirv/1.0/GLSL.std.450.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/OpenCL.std.h b/third_party/spirv-headers/include/spirv/1.0/OpenCL.std.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/OpenCL.std.h rename to third_party/spirv-headers/include/spirv/1.0/OpenCL.std.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/extinst.glsl.std.450.grammar.json b/third_party/spirv-headers/include/spirv/1.0/extinst.glsl.std.450.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/extinst.glsl.std.450.grammar.json rename to third_party/spirv-headers/include/spirv/1.0/extinst.glsl.std.450.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/extinst.opencl.std.100.grammar.json b/third_party/spirv-headers/include/spirv/1.0/extinst.opencl.std.100.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/extinst.opencl.std.100.grammar.json rename to third_party/spirv-headers/include/spirv/1.0/extinst.opencl.std.100.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.core.grammar.json b/third_party/spirv-headers/include/spirv/1.0/spirv.core.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.core.grammar.json rename to third_party/spirv-headers/include/spirv/1.0/spirv.core.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.cs b/third_party/spirv-headers/include/spirv/1.0/spirv.cs similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.cs rename to third_party/spirv-headers/include/spirv/1.0/spirv.cs diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.h b/third_party/spirv-headers/include/spirv/1.0/spirv.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.h rename to third_party/spirv-headers/include/spirv/1.0/spirv.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.hpp b/third_party/spirv-headers/include/spirv/1.0/spirv.hpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.hpp rename to third_party/spirv-headers/include/spirv/1.0/spirv.hpp diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.hpp11 b/third_party/spirv-headers/include/spirv/1.0/spirv.hpp11 similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.hpp11 rename to third_party/spirv-headers/include/spirv/1.0/spirv.hpp11 diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.json b/third_party/spirv-headers/include/spirv/1.0/spirv.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.json rename to third_party/spirv-headers/include/spirv/1.0/spirv.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.lua b/third_party/spirv-headers/include/spirv/1.0/spirv.lua similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.lua rename to third_party/spirv-headers/include/spirv/1.0/spirv.lua diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.py b/third_party/spirv-headers/include/spirv/1.0/spirv.py similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.0/spirv.py rename to third_party/spirv-headers/include/spirv/1.0/spirv.py diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/GLSL.std.450.h b/third_party/spirv-headers/include/spirv/1.1/GLSL.std.450.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/GLSL.std.450.h rename to third_party/spirv-headers/include/spirv/1.1/GLSL.std.450.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/OpenCL.std.h b/third_party/spirv-headers/include/spirv/1.1/OpenCL.std.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/OpenCL.std.h rename to third_party/spirv-headers/include/spirv/1.1/OpenCL.std.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/extinst.glsl.std.450.grammar.json b/third_party/spirv-headers/include/spirv/1.1/extinst.glsl.std.450.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/extinst.glsl.std.450.grammar.json rename to third_party/spirv-headers/include/spirv/1.1/extinst.glsl.std.450.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/extinst.opencl.std.100.grammar.json b/third_party/spirv-headers/include/spirv/1.1/extinst.opencl.std.100.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/extinst.opencl.std.100.grammar.json rename to third_party/spirv-headers/include/spirv/1.1/extinst.opencl.std.100.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.core.grammar.json b/third_party/spirv-headers/include/spirv/1.1/spirv.core.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.core.grammar.json rename to third_party/spirv-headers/include/spirv/1.1/spirv.core.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.cs b/third_party/spirv-headers/include/spirv/1.1/spirv.cs similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.cs rename to third_party/spirv-headers/include/spirv/1.1/spirv.cs diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.h b/third_party/spirv-headers/include/spirv/1.1/spirv.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.h rename to third_party/spirv-headers/include/spirv/1.1/spirv.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.hpp b/third_party/spirv-headers/include/spirv/1.1/spirv.hpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.hpp rename to third_party/spirv-headers/include/spirv/1.1/spirv.hpp diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.hpp11 b/third_party/spirv-headers/include/spirv/1.1/spirv.hpp11 similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.hpp11 rename to third_party/spirv-headers/include/spirv/1.1/spirv.hpp11 diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.json b/third_party/spirv-headers/include/spirv/1.1/spirv.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.json rename to third_party/spirv-headers/include/spirv/1.1/spirv.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.lua b/third_party/spirv-headers/include/spirv/1.1/spirv.lua similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.lua rename to third_party/spirv-headers/include/spirv/1.1/spirv.lua diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.py b/third_party/spirv-headers/include/spirv/1.1/spirv.py similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.1/spirv.py rename to third_party/spirv-headers/include/spirv/1.1/spirv.py diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/GLSL.std.450.h b/third_party/spirv-headers/include/spirv/1.2/GLSL.std.450.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/GLSL.std.450.h rename to third_party/spirv-headers/include/spirv/1.2/GLSL.std.450.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/OpenCL.std.h b/third_party/spirv-headers/include/spirv/1.2/OpenCL.std.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/OpenCL.std.h rename to third_party/spirv-headers/include/spirv/1.2/OpenCL.std.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/extinst.glsl.std.450.grammar.json b/third_party/spirv-headers/include/spirv/1.2/extinst.glsl.std.450.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/extinst.glsl.std.450.grammar.json rename to third_party/spirv-headers/include/spirv/1.2/extinst.glsl.std.450.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/extinst.opencl.std.100.grammar.json b/third_party/spirv-headers/include/spirv/1.2/extinst.opencl.std.100.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/extinst.opencl.std.100.grammar.json rename to third_party/spirv-headers/include/spirv/1.2/extinst.opencl.std.100.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.core.grammar.json b/third_party/spirv-headers/include/spirv/1.2/spirv.core.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.core.grammar.json rename to third_party/spirv-headers/include/spirv/1.2/spirv.core.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.cs b/third_party/spirv-headers/include/spirv/1.2/spirv.cs similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.cs rename to third_party/spirv-headers/include/spirv/1.2/spirv.cs diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.h b/third_party/spirv-headers/include/spirv/1.2/spirv.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.h rename to third_party/spirv-headers/include/spirv/1.2/spirv.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.hpp b/third_party/spirv-headers/include/spirv/1.2/spirv.hpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.hpp rename to third_party/spirv-headers/include/spirv/1.2/spirv.hpp diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.hpp11 b/third_party/spirv-headers/include/spirv/1.2/spirv.hpp11 similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.hpp11 rename to third_party/spirv-headers/include/spirv/1.2/spirv.hpp11 diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.json b/third_party/spirv-headers/include/spirv/1.2/spirv.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.json rename to third_party/spirv-headers/include/spirv/1.2/spirv.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.lua b/third_party/spirv-headers/include/spirv/1.2/spirv.lua similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.lua rename to third_party/spirv-headers/include/spirv/1.2/spirv.lua diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.py b/third_party/spirv-headers/include/spirv/1.2/spirv.py similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/1.2/spirv.py rename to third_party/spirv-headers/include/spirv/1.2/spirv.py diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/spir-v.xml b/third_party/spirv-headers/include/spirv/spir-v.xml similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/spir-v.xml rename to third_party/spirv-headers/include/spirv/spir-v.xml diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_gcn_shader.h b/third_party/spirv-headers/include/spirv/unified1/AMD_gcn_shader.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_gcn_shader.h rename to third_party/spirv-headers/include/spirv/unified1/AMD_gcn_shader.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_shader_ballot.h b/third_party/spirv-headers/include/spirv/unified1/AMD_shader_ballot.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_shader_ballot.h rename to third_party/spirv-headers/include/spirv/unified1/AMD_shader_ballot.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_shader_explicit_vertex_parameter.h b/third_party/spirv-headers/include/spirv/unified1/AMD_shader_explicit_vertex_parameter.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_shader_explicit_vertex_parameter.h rename to third_party/spirv-headers/include/spirv/unified1/AMD_shader_explicit_vertex_parameter.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_shader_trinary_minmax.h b/third_party/spirv-headers/include/spirv/unified1/AMD_shader_trinary_minmax.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/AMD_shader_trinary_minmax.h rename to third_party/spirv-headers/include/spirv/unified1/AMD_shader_trinary_minmax.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/DebugInfo.h b/third_party/spirv-headers/include/spirv/unified1/DebugInfo.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/DebugInfo.h rename to third_party/spirv-headers/include/spirv/unified1/DebugInfo.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/GLSL.std.450.h b/third_party/spirv-headers/include/spirv/unified1/GLSL.std.450.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/GLSL.std.450.h rename to third_party/spirv-headers/include/spirv/unified1/GLSL.std.450.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/NonSemanticClspvReflection.h b/third_party/spirv-headers/include/spirv/unified1/NonSemanticClspvReflection.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/NonSemanticClspvReflection.h rename to third_party/spirv-headers/include/spirv/unified1/NonSemanticClspvReflection.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/NonSemanticDebugPrintf.h b/third_party/spirv-headers/include/spirv/unified1/NonSemanticDebugPrintf.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/NonSemanticDebugPrintf.h rename to third_party/spirv-headers/include/spirv/unified1/NonSemanticDebugPrintf.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/NonSemanticShaderDebugInfo100.h b/third_party/spirv-headers/include/spirv/unified1/NonSemanticShaderDebugInfo100.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/NonSemanticShaderDebugInfo100.h rename to third_party/spirv-headers/include/spirv/unified1/NonSemanticShaderDebugInfo100.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/OpenCL.std.h b/third_party/spirv-headers/include/spirv/unified1/OpenCL.std.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/OpenCL.std.h rename to third_party/spirv-headers/include/spirv/unified1/OpenCL.std.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/OpenCLDebugInfo100.h b/third_party/spirv-headers/include/spirv/unified1/OpenCLDebugInfo100.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/OpenCLDebugInfo100.h rename to third_party/spirv-headers/include/spirv/unified1/OpenCLDebugInfo100.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.debuginfo.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.debuginfo.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.debuginfo.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.debuginfo.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.glsl.std.450.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.glsl.std.450.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.glsl.std.450.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.glsl.std.450.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.nonsemantic.clspvreflection.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.nonsemantic.clspvreflection.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.nonsemantic.clspvreflection.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.nonsemantic.clspvreflection.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.nonsemantic.debugprintf.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.nonsemantic.debugprintf.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.nonsemantic.debugprintf.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.nonsemantic.debugprintf.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.opencl.std.100.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.opencl.std.100.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.opencl.std.100.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.opencl.std.100.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-gcn-shader.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-gcn-shader.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-gcn-shader.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-gcn-shader.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-ballot.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-ballot.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-ballot.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-ballot.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-trinary-minmax.grammar.json b/third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-trinary-minmax.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-trinary-minmax.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/extinst.spv-amd-shader-trinary-minmax.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.bf b/third_party/spirv-headers/include/spirv/unified1/spirv.bf similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.bf rename to third_party/spirv-headers/include/spirv/unified1/spirv.bf diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.core.grammar.json b/third_party/spirv-headers/include/spirv/unified1/spirv.core.grammar.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.core.grammar.json rename to third_party/spirv-headers/include/spirv/unified1/spirv.core.grammar.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.cs b/third_party/spirv-headers/include/spirv/unified1/spirv.cs similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.cs rename to third_party/spirv-headers/include/spirv/unified1/spirv.cs diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.h b/third_party/spirv-headers/include/spirv/unified1/spirv.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.h rename to third_party/spirv-headers/include/spirv/unified1/spirv.h diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.hpp b/third_party/spirv-headers/include/spirv/unified1/spirv.hpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.hpp rename to third_party/spirv-headers/include/spirv/unified1/spirv.hpp diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.hpp11 b/third_party/spirv-headers/include/spirv/unified1/spirv.hpp11 similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.hpp11 rename to third_party/spirv-headers/include/spirv/unified1/spirv.hpp11 diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.json b/third_party/spirv-headers/include/spirv/unified1/spirv.json similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.json rename to third_party/spirv-headers/include/spirv/unified1/spirv.json diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.lua b/third_party/spirv-headers/include/spirv/unified1/spirv.lua similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.lua rename to third_party/spirv-headers/include/spirv/unified1/spirv.lua diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.py b/third_party/spirv-headers/include/spirv/unified1/spirv.py similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spirv.py rename to third_party/spirv-headers/include/spirv/unified1/spirv.py diff --git a/third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spv.d b/third_party/spirv-headers/include/spirv/unified1/spv.d similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/include/spirv/unified1/spv.d rename to third_party/spirv-headers/include/spirv/unified1/spv.d diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/CMakeLists.txt b/third_party/spirv-headers/tools/buildHeaders/CMakeLists.txt similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/CMakeLists.txt rename to third_party/spirv-headers/tools/buildHeaders/CMakeLists.txt diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/bin/generate_language_headers.py b/third_party/spirv-headers/tools/buildHeaders/bin/generate_language_headers.py similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/bin/generate_language_headers.py rename to third_party/spirv-headers/tools/buildHeaders/bin/generate_language_headers.py diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/bin/makeExtinstHeaders.py b/third_party/spirv-headers/tools/buildHeaders/bin/makeExtinstHeaders.py similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/bin/makeExtinstHeaders.py rename to third_party/spirv-headers/tools/buildHeaders/bin/makeExtinstHeaders.py diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/bin/makeHeaders b/third_party/spirv-headers/tools/buildHeaders/bin/makeHeaders similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/bin/makeHeaders rename to third_party/spirv-headers/tools/buildHeaders/bin/makeHeaders diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/header.cpp b/third_party/spirv-headers/tools/buildHeaders/header.cpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/header.cpp rename to third_party/spirv-headers/tools/buildHeaders/header.cpp diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/header.h b/third_party/spirv-headers/tools/buildHeaders/header.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/header.h rename to third_party/spirv-headers/tools/buildHeaders/header.h diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/jsonToSpirv.cpp b/third_party/spirv-headers/tools/buildHeaders/jsonToSpirv.cpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/jsonToSpirv.cpp rename to third_party/spirv-headers/tools/buildHeaders/jsonToSpirv.cpp diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/jsonToSpirv.h b/third_party/spirv-headers/tools/buildHeaders/jsonToSpirv.h similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/jsonToSpirv.h rename to third_party/spirv-headers/tools/buildHeaders/jsonToSpirv.h diff --git a/third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/main.cpp b/third_party/spirv-headers/tools/buildHeaders/main.cpp similarity index 100% rename from third_party/spirv-tools/external/spirv-headers/tools/buildHeaders/main.cpp rename to third_party/spirv-headers/tools/buildHeaders/main.cpp diff --git a/third_party/spirv-tools/.github/workflows/wasm.yml b/third_party/spirv-tools/.github/workflows/wasm.yml deleted file mode 100644 index d9a9c5cb7c1..00000000000 --- a/third_party/spirv-tools/.github/workflows/wasm.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Wasm Build - -on: [ push, pull_request ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Build web - run: docker-compose up - - name: Run tests - run: node test/wasm/test.js diff --git a/third_party/spirv-tools/.gitignore b/third_party/spirv-tools/.gitignore index a87496a7d97..ec709ba79df 100644 --- a/third_party/spirv-tools/.gitignore +++ b/third_party/spirv-tools/.gitignore @@ -5,6 +5,8 @@ compile_commands.json /build*/ /buildtools/ /external/googletest +/external/SPIRV-Headers +/external/spirv-headers /external/effcee /external/re2 /external/protobuf diff --git a/third_party/spirv-tools/CMakeLists.txt b/third_party/spirv-tools/CMakeLists.txt index 8437fd570a3..487a40a4816 100755 --- a/third_party/spirv-tools/CMakeLists.txt +++ b/third_party/spirv-tools/CMakeLists.txt @@ -24,6 +24,8 @@ if (POLICY CMP0054) endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../spirv-headers) + if (APPLE) set(CMAKE_MACOSX_RPATH ON) endif (APPLE) @@ -106,6 +108,7 @@ set(SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS "" CACHE STRING "Used by OSS-Fuzz to c option(SPIRV_BUILD_LIBFUZZER_TARGETS "Build libFuzzer targets" OFF) set(SPIRV_WERROR OFF) + if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND (NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC"))) set(COMPILER_IS_LIKE_GNU TRUE) endif() @@ -313,8 +316,6 @@ option(SPIRV_SKIP_EXECUTABLES ${SPIRV_SKIP_EXECUTABLES_OPTION}) option(SPIRV_SKIP_TESTS "Skip building tests along with the library" ${SPIRV_SKIP_TESTS_OPTION}) - - if ("${SPIRV_SKIP_EXECUTABLES}") set(SPIRV_SKIP_TESTS ON) endif() @@ -378,4 +379,3 @@ endif() set(SPIRV_LIBRARIES "-lSPIRV-Tools-opt -lSPIRV-Tools -lSPIRV-Tools-link") set(SPIRV_SHARED_LIBRARIES "-lSPIRV-Tools-shared") - diff --git a/third_party/spirv-tools/FILAMENT_README.md b/third_party/spirv-tools/FILAMENT_README.md index 5020b06c78e..9c9bb3e437b 100644 --- a/third_party/spirv-tools/FILAMENT_README.md +++ b/third_party/spirv-tools/FILAMENT_README.md @@ -1,34 +1,20 @@ -When updating spirv-tools to a new version there are several `CMakeLists.txt` files that need -to patched with Filament specific changes. This can be done by running the following command at -Filament's root: +When updating SPIRV-Tools to a new version, run the following from *this* directory ``` -git apply third_party/spirv-tools/filament-specific-changes.patch +bash filament-update.sh [git commit hash] ``` -The following procedure can be used to update spirv-tools. Note that there is a secondary repository -that needs to be downloaded (spirv-headers). +This will pull in the updated source for SPRIV-Tools and pull in the right version of SPIRV-Headers. +The script will also try to apply Filament specific changes to the `CMakeLists.txt`. It could be +that the diff application will fail, in which case, the updater will need to resolve the difference +manually and update `filament-specific-changes.patch`. -``` -curl -L https://github.com/KhronosGroup/spirv-tools/archive/master.zip > master.zip -unzip master.zip -rsync -r SPIRV-Tools-master/ spirv-tools/ --delete -rm -rf SPIRV-Tools-master master.zip -curl -L https://github.com/KhronosGroup/spirv-headers/archive/master.zip > master.zip -unzip master.zip -mv SPIRV-Headers-master/ spirv-tools/external/spirv-headers -rm master.zip +The above script will bring in the changes, but you would still need to add it to a git commit +(i.e. pull request) by doing -git add spirv-tools +``` +git add -u third_party/spriv-tools third_party/spirv-headers ``` -The patch file also edits the `.gitignore` to allow `spirv-headers` to be committed. - -Finally, remember to bring back the Filament-specific changes in CMakeLists. - -To restore this file and the patch file do: +from the Filament source root. -``` -git checkout main spirv-tools/FILAMENT_README.md -git checkout main spirv-tools/filament-specific-changes.patch -``` diff --git a/third_party/spirv-tools/external/CMakeLists.txt b/third_party/spirv-tools/external/CMakeLists.txt index a081e119bf3..179a4012f94 100644 --- a/third_party/spirv-tools/external/CMakeLists.txt +++ b/third_party/spirv-tools/external/CMakeLists.txt @@ -45,6 +45,8 @@ if (IS_DIRECTORY ${SPIRV_HEADER_DIR}) # Do this so enclosing projects can use SPIRV-Headers_SOURCE_DIR to find # headers to include. if (NOT DEFINED SPIRV-Headers_SOURCE_DIR) + set(SPIRV_HEADERS_SKIP_INSTALL ON) + set(SPIRV_HEADERS_SKIP_EXAMPLES ON) add_subdirectory(${SPIRV_HEADER_DIR}) endif() else() diff --git a/third_party/spirv-tools/external/spirv-headers/.github/workflows/presubmit.yml b/third_party/spirv-tools/external/spirv-headers/.github/workflows/presubmit.yml deleted file mode 100644 index d9c25faef5e..00000000000 --- a/third_party/spirv-tools/external/spirv-headers/.github/workflows/presubmit.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Presubmit -on: [push, pull_request] - -jobs: - build: - name: Build ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - uses: actions/checkout@v2 - - name: Install Ubuntu packages - if: matrix.os == 'ubuntu-latest' - run: sudo apt install -y dos2unix - - name: Install macOS packages - if: matrix.os == 'macos-latest' - run: brew install dos2unix - - name: Build - run: | - mkdir build - cd build - cmake -DCMAKE_INSTALL_PREFIX=install .. - cmake --build . --target install - - name: Build spec tools - run: | - cd tools/buildHeaders - mkdir build - cd build - cmake .. - cmake --build . --target install - - name: Build headers - run: | - cd tools/buildHeaders - ./bin/makeHeaders - - name: Check generated headers - run: git diff --exit-code diff --git a/third_party/spirv-tools/filament-specific-changes.patch b/third_party/spirv-tools/filament-specific-changes.patch index a6d1fb52135..8f545857427 100644 --- a/third_party/spirv-tools/filament-specific-changes.patch +++ b/third_party/spirv-tools/filament-specific-changes.patch @@ -1,24 +1,22 @@ -diff --git a/third_party/spirv-tools/.gitignore b/third_party/spirv-tools/.gitignore -index ec709ba79..a87496a7d 100644 ---- a/third_party/spirv-tools/.gitignore -+++ b/third_party/spirv-tools/.gitignore -@@ -5,8 +5,6 @@ compile_commands.json - /build*/ - /buildtools/ - /external/googletest --/external/SPIRV-Headers --/external/spirv-headers - /external/effcee - /external/re2 - /external/protobuf diff --git a/third_party/spirv-tools/CMakeLists.txt b/third_party/spirv-tools/CMakeLists.txt index 76b87d8c5..53b3404d1 100755 --- a/third_party/spirv-tools/CMakeLists.txt +++ b/third_party/spirv-tools/CMakeLists.txt -@@ -24,8 +24,14 @@ if (POLICY CMP0054) +@@ -12,7 +12,7 @@ + # See the License for the specific language governing permissions and + # limitations under the License. + +-cmake_minimum_required(VERSION 2.8.12) ++cmake_minimum_required(VERSION 3.19) + if (POLICY CMP0048) + cmake_policy(SET CMP0048 NEW) + endif() +@@ -24,8 +24,16 @@ if (POLICY CMP0054) endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) - + ++set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../spirv-headers) ++ +if (APPLE) + set(CMAKE_MACOSX_RPATH ON) +endif (APPLE) @@ -63,7 +61,7 @@ index 76b87d8c5..53b3404d1 100755 # Check for symbol exports on Linux. # At the moment, this check will fail on the OSX build machines for the Android NDK. -@@ -286,11 +297,13 @@ if(ENABLE_SPIRV_TOOLS_INSTALL) +@@ -286,11 +297,11 @@ if(ENABLE_SPIRV_TOOLS_INSTALL) endif() # Defaults to OFF if the user didn't set it. @@ -72,13 +70,11 @@ index 76b87d8c5..53b3404d1 100755 - ${SPIRV_SKIP_EXECUTABLES}) -option(SPIRV_SKIP_TESTS - "Skip building tests along with the library" ${SPIRV_SKIP_TESTS}) -+ option(SPIRV_SKIP_EXECUTABLES -+ "Skip building the executable and tests along with the library" -+ ${SPIRV_SKIP_EXECUTABLES_OPTION}) -+ option(SPIRV_SKIP_TESTS -+ "Skip building tests along with the library" ${SPIRV_SKIP_TESTS_OPTION}) -+ -+ ++option(SPIRV_SKIP_EXECUTABLES ++ "Skip building the executable and tests along with the library" ++ ${SPIRV_SKIP_EXECUTABLES_OPTION}) ++option(SPIRV_SKIP_TESTS ++ "Skip building tests along with the library" ${SPIRV_SKIP_TESTS_OPTION}) if ("${SPIRV_SKIP_EXECUTABLES}") set(SPIRV_SKIP_TESTS ON) endif() @@ -131,36 +127,6 @@ index 76b87d8c5..53b3404d1 100755 - DESTINATION - ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -endif() -diff --git a/third_party/spirv-tools/FILAMENT_README.md b/third_party/spirv-tools/FILAMENT_README.md -index 39e4620a0..3aa363ebf 100644 ---- a/third_party/spirv-tools/FILAMENT_README.md -+++ b/third_party/spirv-tools/FILAMENT_README.md -@@ -1,9 +1,6 @@ --When updating spirv-tools to a new version, make sure to preserve all the changes marked with --the following in `CMakeLists.txt` and `source/CMakeLists.txt`: -- --`# Filament specific changes` -- --You can easily apply these changes by running the following command at Filament's root: -+When updating spirv-tools to a new version there are several `CMakeLists.txt` files that need -+to patched with Filament specific changes. This can be done by running the following command at -+Filament's root: - - ``` - git apply third_party/spirv-tools/filament-specific-changes.patch -diff --git a/third_party/spirv-tools/external/CMakeLists.txt b/third_party/spirv-tools/external/CMakeLists.txt -index 179a4012f..a081e119b 100644 ---- a/third_party/spirv-tools/external/CMakeLists.txt -+++ b/third_party/spirv-tools/external/CMakeLists.txt -@@ -45,8 +45,6 @@ if (IS_DIRECTORY ${SPIRV_HEADER_DIR}) - # Do this so enclosing projects can use SPIRV-Headers_SOURCE_DIR to find - # headers to include. - if (NOT DEFINED SPIRV-Headers_SOURCE_DIR) -- set(SPIRV_HEADERS_SKIP_INSTALL ON) -- set(SPIRV_HEADERS_SKIP_EXAMPLES ON) - add_subdirectory(${SPIRV_HEADER_DIR}) - endif() - else() diff --git a/third_party/spirv-tools/source/CMakeLists.txt b/third_party/spirv-tools/source/CMakeLists.txt index 98559b8fe..0734d6a82 100644 --- a/third_party/spirv-tools/source/CMakeLists.txt @@ -194,21 +160,3 @@ index 98559b8fe..0734d6a82 100644 if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") -diff --git a/third_party/spirv-tools/external/spirv-headers/CMakeLists.txt b/third_party/spirv-tools/external/spirv-headers/CMakeLists.txt -index 147f7adee..d8d1d7e35 100644 ---- a/third_party/spirv-tools/external/spirv-headers/CMakeLists.txt -+++ b/third_party/spirv-tools/external/spirv-headers/CMakeLists.txt -@@ -49,11 +49,9 @@ add_custom_target(install-headers - COMMAND cmake -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv - $ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/include/spirv) - --option(SPIRV_HEADERS_SKIP_EXAMPLES "Skip building examples" -- ${SPIRV_HEADERS_SKIP_EXAMPLES}) -+option(SPIRV_HEADERS_SKIP_EXAMPLES "Skip building examples" ON) - --option(SPIRV_HEADERS_SKIP_INSTALL "Skip install" -- ${SPIRV_HEADERS_SKIP_INSTALL}) -+option(SPIRV_HEADERS_SKIP_INSTALL "Skip install" ON) - - if(NOT ${SPIRV_HEADERS_SKIP_EXAMPLES}) - set(SPIRV_HEADERS_ENABLE_EXAMPLES ON) diff --git a/third_party/spirv-tools/filament-update.sh b/third_party/spirv-tools/filament-update.sh new file mode 100644 index 00000000000..95149ccd442 --- /dev/null +++ b/third_party/spirv-tools/filament-update.sh @@ -0,0 +1,54 @@ +# Copyright (C) 2023 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. + + +# This script is used to update SPIRV-Tools from source. +# This script takes in a git commit hash as an argument. + +#!/usr/bin/env bash + +TOOLS_HASH=$1 + +function sync_khronos_repo() { + local REPO=$1 + local HASH=$2 + pushd . + cd /tmp + rm -rf ${REPO} && git clone git@github.com:KhronosGroup/${REPO}.git + cd ${REPO} + git reset --hard ${HASH} + popd +} + +# First we update SPIRV-Tools to the given git hash +sync_khronos_repo SPIRV-Tools ${TOOLS_HASH} + +rsync -r /tmp/SPIRV-Tools/ ./ --delete +rm -rf .git .github +# Recover the filament specific files lost in the above rsync +git checkout filament-specific-changes.patch FILAMENT_README.md filament-update.sh +git apply filament-specific-changes.patch + +HEADERS_HASH=`grep "spirv_headers_revision':" DEPS | awk '{ print $2 }' | sed "s/[\'\,\\n]//g"` + +# Next we update SPIRV-Headers to the right hash (dependency as given by SPIRV-Tools) +sync_khronos_repo SPIRV-Headers ${HEADERS_HASH} + +pushd . +cd ../spirv-headers +rsync -r /tmp/SPIRV-Headers/ ./ --delete +rm -rf .git .github +# Recover the filament specific files lost in the above rsync +git checkout filament-specific-changes.patch FILAMENT_README.md +git apply filament-specific-changes.patch +popd diff --git a/tools/matc/src/matc/CommandlineConfig.cpp b/tools/matc/src/matc/CommandlineConfig.cpp index 5323fb87419..64850c436ee 100644 --- a/tools/matc/src/matc/CommandlineConfig.cpp +++ b/tools/matc/src/matc/CommandlineConfig.cpp @@ -62,6 +62,9 @@ static void usage(char* name) { " MATC --api opengl --api metal ...\n\n" " --feature-level, -l\n" " Specify the maximum feature level allowed (default is 3).\n\n" + " --no-essl1, -1\n" + " Don't generate ESSL 1.0 code even for Feature Level 0 mobile shaders.\n" + " Shaders are still validated against ESSL 1.0.\n\n" " --define, -D\n" " Add a preprocessor define macro via =. defaults to 1 if omitted.\n" " Can be repeated to specify multiple definitions:\n" @@ -71,6 +74,11 @@ static void usage(char* name) { " Unlike --define, this applies to the material specification, not GLSL.\n" " Can be repeated to specify multiple macros:\n" " MATC -TBLENDING=fade -TDOUBLESIDED=false ...\n\n" + " --material-parameter =, -P=\n" + " Set the material property pointed to by to \n" + " This overwrites the value configured in the material file.\n" + " Material property of array type is not supported.\n" + " MATC -PflipUV=false -PshadingModel=lit -Pname=myMat ...\n\n" " --reflect, -r\n" " Reflect the specified metadata as JSON: parameters\n\n" " --variant-filter=, -V \n" @@ -171,7 +179,7 @@ static void parseDefine(std::string defineString, Config::StringReplacementMap& } bool CommandlineConfig::parse() { - static constexpr const char* OPTSTR = "hLxo:f:dm:a:l:p:D:T:OSEr:vV:gtwF"; + static constexpr const char* OPTSTR = "hLxo:f:dm:a:l:p:D:T:P:OSEr:vV:gtwF1"; static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, { "license", no_argument, nullptr, 'L' }, @@ -187,8 +195,10 @@ bool CommandlineConfig::parse() { { "preprocessor-only", no_argument, nullptr, 'E' }, { "api", required_argument, nullptr, 'a' }, { "feature-level", required_argument, nullptr, 'l' }, + { "no-essl1", no_argument, nullptr, '1' }, { "define", required_argument, nullptr, 'D' }, { "template", required_argument, nullptr, 'T' }, + { "material-parameter",required_argument, nullptr, 'P' }, { "reflect", required_argument, nullptr, 'r' }, { "print", no_argument, nullptr, 't' }, { "version", no_argument, nullptr, 'v' }, @@ -270,12 +280,18 @@ bool CommandlineConfig::parse() { } break; } + case '1': + mIncludeEssl1 = false; + break; case 'D': parseDefine(arg, mDefines); break; case 'T': parseDefine(arg, mTemplateMap); break; + case 'P': + parseDefine(arg, mMaterialParameters); + break; case 'v': // Similar to --help, the --version command does an early exit in order to avoid // subsequent error spew such as "Missing input filename" etc. diff --git a/tools/matc/src/matc/Config.h b/tools/matc/src/matc/Config.h index c5403d693d3..275799e0814 100644 --- a/tools/matc/src/matc/Config.h +++ b/tools/matc/src/matc/Config.h @@ -41,7 +41,7 @@ class Config { using TargetApi = filamat::MaterialBuilder::TargetApi; using Optimization = filamat::MaterialBuilder::Optimization; - // For defines and template args, we use an ordered map with a transparent comparator. + // For defines, template, and material parameters, we use an ordered map with a transparent comparator. // Even though the key is stored using std::string, this allows you to make lookups using // std::string_view. There is no need to construct a std::string object just to make a lookup. using StringReplacementMap = std::map>; @@ -119,6 +119,10 @@ class Config { return mNoSamplerValidation; } + bool includeEssl1() const noexcept { + return mIncludeEssl1; + } + filament::UserVariantFilterMask getVariantFilter() const noexcept { return mVariantFilter; } @@ -131,6 +135,10 @@ class Config { return mTemplateMap; } + const StringReplacementMap& getMaterialParameters() const noexcept { + return mMaterialParameters; + } + filament::backend::FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } @@ -149,7 +157,9 @@ class Config { filament::backend::FeatureLevel mFeatureLevel = filament::backend::FeatureLevel::FEATURE_LEVEL_3; StringReplacementMap mDefines; StringReplacementMap mTemplateMap; + StringReplacementMap mMaterialParameters; filament::UserVariantFilterMask mVariantFilter = 0; + bool mIncludeEssl1 = true; }; } diff --git a/tools/matc/src/matc/JsonishLexer.cpp b/tools/matc/src/matc/JsonishLexer.cpp index 3cca02e560f..2173693c0a2 100644 --- a/tools/matc/src/matc/JsonishLexer.cpp +++ b/tools/matc/src/matc/JsonishLexer.cpp @@ -27,9 +27,7 @@ JsonType JsonishLexer::readIdentifier() noexcept { consume(); } - const char* lexemeEnd = mCursor - 1; - - size_t lexemeSize = lexemeEnd - lexemeStart; + size_t lexemeSize = mCursor - lexemeStart; // Check what kind of keyword we got here. if (strncmp("true", lexemeStart, lexemeSize) == 0) { diff --git a/tools/matc/src/matc/MaterialCompiler.cpp b/tools/matc/src/matc/MaterialCompiler.cpp index 2785a733439..c528326aee3 100644 --- a/tools/matc/src/matc/MaterialCompiler.cpp +++ b/tools/matc/src/matc/MaterialCompiler.cpp @@ -34,6 +34,7 @@ #include "ParametersProcessor.h" #include +#include "glslang/Include/intermediate.h" #include "sca/builtinResource.h" @@ -228,7 +229,9 @@ static bool reflectParameters(const MaterialBuilder& builder) { std::cout << R"( "format": ")" << Enums::toString(parameter.format) << "\"," << std::endl; std::cout << R"( "precision": ")" << - Enums::toString(parameter.precision) << "\"" << std::endl; + Enums::toString(parameter.precision) << "\"," << std::endl; + std::cout << R"( "multisample": ")" << + (parameter.multisample ? "true" : "false")<< "\"" << std::endl; } else if (parameter.isUniform()) { std::cout << R"( "type": ")" << Enums::toString(parameter.uniformType) << "\"," << std::endl; @@ -398,6 +401,7 @@ bool MaterialCompiler::run(const Config& config) { builder .noSamplerValidation(config.noSamplerValidation()) + .includeEssl1(config.includeEssl1()) .includeCallback(includer) .fileName(materialFilePath.getName().c_str()) .platform(config.getPlatform()) @@ -411,6 +415,11 @@ bool MaterialCompiler::run(const Config& config) { builder.shaderDefine(define.first.c_str(), define.second.c_str()); } + if (!processMaterialParameters(builder, config)) { + std::cerr << "Error while processing material parameters." << std::endl; + return false; + } + JobSystem js; js.adopt(); @@ -616,4 +625,14 @@ bool MaterialCompiler::compileRawShader(const char* glsl, size_t size, bool isDe return true; } +bool MaterialCompiler::processMaterialParameters(filamat::MaterialBuilder& builder, + const Config& config) const { + ParametersProcessor parametersProcessor; + bool ok = true; + for (const auto& param : config.getMaterialParameters()) { + ok &= parametersProcessor.process(builder, param.first, param.second); + } + return ok; +} + } // namespace matc diff --git a/tools/matc/src/matc/MaterialCompiler.h b/tools/matc/src/matc/MaterialCompiler.h index 36147376e9a..583f6cce28a 100644 --- a/tools/matc/src/matc/MaterialCompiler.h +++ b/tools/matc/src/matc/MaterialCompiler.h @@ -70,6 +70,8 @@ class MaterialCompiler final: public Compiler { bool compileRawShader(const char* glsl, size_t size, bool isDebug, Config::Output* output, const char* ext) const noexcept; + bool processMaterialParameters(filamat::MaterialBuilder& builder, const Config& config) const; + // Member function pointer type, this is used to implement a Command design // pattern. using MaterialConfigProcessor = bool (MaterialCompiler::*) diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index b5ba2cee9cb..828597e51a6 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -160,7 +160,6 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json std::cerr << "parameters: precision must be a STRING." << std::endl; return false; } - auto precisionString = precisionValue->toJsonString(); if (!Enums::isValid(precisionString->getString())){ return logEnumIssue("parameters", *precisionString, Enums::map()); @@ -173,20 +172,27 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json std::cerr << "parameters: format must be a STRING." << std::endl; return false; } - auto formatString = formatValue->toJsonString(); if (!Enums::isValid(formatString->getString())){ return logEnumIssue("parameters", *formatString, Enums::map()); } } + const JsonishValue* multiSampleValue = jsonObject.getValue("multisample"); + if (multiSampleValue) { + if (multiSampleValue->getType() != JsonishValue::BOOL) { + std::cerr << "parameters: multisample must be a BOOL." << std::endl; + return false; + } + } + auto typeString = typeValue->toJsonString()->getString(); auto nameString = nameValue->toJsonString()->getString(); - size_t arraySize = extractArraySize(typeString); + size_t const arraySize = extractArraySize(typeString); if (Enums::isValid(typeString)) { - MaterialBuilder::UniformType type = Enums::toEnum(typeString); + MaterialBuilder::UniformType const type = Enums::toEnum(typeString); ParameterPrecision precision = ParameterPrecision::DEFAULT; if (precisionValue) { precision = @@ -205,22 +211,18 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json return false; } - MaterialBuilder::SamplerType type = Enums::toEnum(typeString); - if (precisionValue && formatValue) { - auto format = Enums::toEnum(formatValue->toJsonString()->getString()); - auto precision = - Enums::toEnum(precisionValue->toJsonString()->getString()); - builder.parameter(nameString.c_str(), type, format, precision); - } else if (formatValue) { - auto format = Enums::toEnum(formatValue->toJsonString()->getString()); - builder.parameter(nameString.c_str(), type, format); - } else if (precisionValue) { - auto precision = - Enums::toEnum(precisionValue->toJsonString()->getString()); - builder.parameter(nameString.c_str(), type, precision); - } else { - builder.parameter(nameString.c_str(), type); - } + MaterialBuilder::SamplerType const type = Enums::toEnum(typeString); + + auto format = formatValue ? Enums::toEnum( + formatValue->toJsonString()->getString()) : SamplerFormat::FLOAT; + + auto precision = precisionValue ? Enums::toEnum( + precisionValue->toJsonString()->getString()) : ParameterPrecision::DEFAULT; + + auto multisample = multiSampleValue ? multiSampleValue->toJsonBool()->getBool() : false; + + builder.parameter(nameString.c_str(), type, format, precision, multisample); + } else { std::cerr << "parameters: the type '" << typeString << "' for parameter with name '" << nameString << "' is neither a valid uniform " @@ -669,6 +671,7 @@ static bool processBlending(MaterialBuilder& builder, const JsonishValue& value) { "fade", MaterialBuilder::BlendingMode::FADE }, { "multiply", MaterialBuilder::BlendingMode::MULTIPLY }, { "screen", MaterialBuilder::BlendingMode::SCREEN }, + { "custom", MaterialBuilder::BlendingMode::CUSTOM }, }; auto jsonString = value.toJsonString(); if (!isStringValidEnum(strToEnum, jsonString->getString())) { @@ -679,6 +682,52 @@ static bool processBlending(MaterialBuilder& builder, const JsonishValue& value) return true; } +static bool processBlendFunction(MaterialBuilder& builder, const JsonishValue& value) { + static const std::unordered_map strToEnum{ + { "zero", MaterialBuilder::BlendFunction::ZERO }, + { "one", MaterialBuilder::BlendFunction::ONE }, + { "srcColor", MaterialBuilder::BlendFunction::SRC_COLOR }, + { "oneMinusSrcColor", MaterialBuilder::BlendFunction::ONE_MINUS_SRC_COLOR }, + { "dstColor", MaterialBuilder::BlendFunction::DST_COLOR }, + { "oneMinusDstColor", MaterialBuilder::BlendFunction::ONE_MINUS_DST_COLOR }, + { "srcAlpha", MaterialBuilder::BlendFunction::SRC_ALPHA }, + { "oneMinusSrcAlpha", MaterialBuilder::BlendFunction::ONE_MINUS_SRC_ALPHA }, + { "dstAlpha", MaterialBuilder::BlendFunction::DST_ALPHA }, + { "oneMinusDstAlpha", MaterialBuilder::BlendFunction::ONE_MINUS_DST_ALPHA }, + { "srcAlphaSaturate", MaterialBuilder::BlendFunction::SRC_ALPHA_SATURATE }, + }; + + if (value.getType() != JsonishValue::Type::OBJECT) { + std::cerr << "blendFunction must be an OBJECT." << std::endl; + } + + JsonishObject const* const jsonObject = value.toJsonObject(); + + MaterialBuilder::BlendFunction srcRGB, srcA, dstRGB, dstA; + std::pair functions[] = { + { "srcRGB", &srcRGB }, + { "srcA", &srcA }, + { "dstRGB", &dstRGB }, + { "dstA", &dstA }, + }; + + for (auto&& entry : functions) { + const char* key = entry.first; + const JsonishValue* v = jsonObject->getValue(key); + if (!v) { + std::cerr << "blendFunction: entry without '" << key << "' key." << std::endl; + return false; + } + if (v->getType() != JsonishValue::STRING) { + std::cerr << "blendFunction: '" << key << "' value must be STRING." << std::endl; + return false; + } + *entry.second = stringToEnum(strToEnum, v->toJsonString()->getString()); + } + builder.customBlendFunctions(srcRGB, srcA, dstRGB, dstA); + return true; +} + static bool processPostLightingBlending(MaterialBuilder& builder, const JsonishValue& value) { static const std::unordered_map strToEnum { { "add", MaterialBuilder::BlendingMode::ADD }, @@ -794,6 +843,20 @@ static bool processGroupSizes(MaterialBuilder& builder, const JsonishValue& v) { return true; } +static bool processStereoscopicType(MaterialBuilder& builder, const JsonishValue& value) { + static const std::unordered_map strToEnum{ + { "instanced", MaterialBuilder::StereoscopicType::INSTANCED }, + { "multiview", MaterialBuilder::StereoscopicType::MULTIVIEW }, + }; + auto jsonString = value.toJsonString(); + if (!isStringValidEnum(strToEnum, jsonString->getString())) { + return logEnumIssue("stereoscopicType", *jsonString, strToEnum); + } + + builder.stereoscopicType(stringToEnum(strToEnum, jsonString->getString())); + return true; +} + static bool processOutput(MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept { const JsonishValue* nameValue = jsonObject.getValue("name"); @@ -1178,6 +1241,7 @@ ParametersProcessor::ParametersProcessor() { mParameters["variables"] = { &processVariables, Type::ARRAY }; mParameters["requires"] = { &processRequires, Type::ARRAY }; mParameters["blending"] = { &processBlending, Type::STRING }; + mParameters["blendFunction"] = { &processBlendFunction, Type::OBJECT }; mParameters["postLightingBlending"] = { &processPostLightingBlending, Type::STRING }; mParameters["vertexDomain"] = { &processVertexDomain, Type::STRING }; mParameters["culling"] = { &processCulling, Type::STRING }; @@ -1212,6 +1276,7 @@ ParametersProcessor::ParametersProcessor() { mParameters["customSurfaceShading"] = { &processCustomSurfaceShading, Type::BOOL }; mParameters["featureLevel"] = { &processFeatureLevel, Type::NUMBER }; mParameters["groupSize"] = { &processGroupSizes, Type::ARRAY }; + mParameters["stereoscopicType"] = { &processStereoscopicType, Type::STRING }; } bool ParametersProcessor::process(MaterialBuilder& builder, const JsonishObject& jsonObject) { @@ -1234,11 +1299,52 @@ bool ParametersProcessor::process(MaterialBuilder& builder, const JsonishObject& auto fPointer = mParameters[key].callback; bool ok = fPointer(builder, *field); if (!ok) { - std::cerr << "Error while processing material key:\"" << key << "\"" << std::endl; + std::cerr << "Error while processing material json, key:\"" << key << "\"" << std::endl; return false; } } return true; } +bool ParametersProcessor::process(filamat::MaterialBuilder& builder, const std::string& key, const std::string& value) { + if (mParameters.find(key) == mParameters.end()) { + std::cerr << "Ignoring config entry (unknown key): \"" << key << "\"" << std::endl; + return false; + } + + std::unique_ptr var; + switch (mParameters.at(key).rootAssert) { + case JsonishValue::Type::BOOL: { + std::string lower; + std::transform(value.begin(), value.end(), std::back_inserter(lower), ::tolower); + if (lower.empty() || lower == "false" || lower == "f" || lower == "0") { + var = std::make_unique(false); + } + else { + var = std::make_unique(true); + } + break; + } + case JsonishValue::Type::NUMBER: + var = std::make_unique(std::stof(value)); + break; + case JsonishValue::Type::STRING: + var = std::make_unique(value); + break; + default: + std::cerr << "Unsupported type: \"" + << JsonishValue::typeToString(mParameters.at(key).rootAssert) + << "\"" << std::endl; + return false; + } + + auto fPointer = mParameters[key].callback; + bool ok = fPointer(builder, *var); + if (!ok) { + std::cerr << "Error while processing material param, key:\"" << key << "\"" << std::endl; + return false; + } + return true; +} + } // namespace matc diff --git a/tools/matc/src/matc/ParametersProcessor.h b/tools/matc/src/matc/ParametersProcessor.h index ba983c9e75e..a3aa3117d35 100644 --- a/tools/matc/src/matc/ParametersProcessor.h +++ b/tools/matc/src/matc/ParametersProcessor.h @@ -19,6 +19,7 @@ #include #include +#include #include "JsonishLexeme.h" #include "JsonishParser.h" @@ -33,6 +34,7 @@ class ParametersProcessor { ParametersProcessor(); ~ParametersProcessor() = default; bool process(filamat::MaterialBuilder& builder, const JsonishObject& jsonObject); + bool process(filamat::MaterialBuilder& builder, const std::string& key, const std::string& value); private: diff --git a/tools/resgen/src/main.cpp b/tools/resgen/src/main.cpp index 8cae171de37..1f475c49703 100644 --- a/tools/resgen/src/main.cpp +++ b/tools/resgen/src/main.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index 3040bafcd1f..36d0bf988be 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -339,6 +339,12 @@ Filament.loadClassExtensions = function() { this._setGuardBandOptions(options); }; + /// setStereoscopicOptions ::method:: + Filament.View.prototype.setStereoscopicOptions = function(overrides) { + const options = this.setStereoscopicOptionsDefaults(overrides); + this._setStereoscopicOptions(options); + } + /// BufferObject ::core class:: /// setBuffer ::method:: diff --git a/web/filament-js/extensions_generated.js b/web/filament-js/extensions_generated.js index d6e2b18ea7e..a0d69778c46 100644 --- a/web/filament-js/extensions_generated.js +++ b/web/filament-js/extensions_generated.js @@ -60,6 +60,7 @@ Filament.loadGeneratedExtensions = function() { Filament.View.prototype.setDepthOfFieldOptionsDefaults = function(overrides) { const options = { cocScale: 1.0, + cocAspectRatio: 1.0, maxApertureDiameter: 0.01, enabled: false, filter: Filament.View$DepthOfFieldOptions$Filter.MEDIAN, @@ -138,8 +139,20 @@ Filament.loadGeneratedExtensions = function() { Filament.View.prototype.setTemporalAntiAliasingOptionsDefaults = function(overrides) { const options = { filterWidth: 1.0, - feedback: 0.04, + feedback: 0.12, + lodBias: -1.0, + sharpness: 0.0, enabled: false, + upscaling: false, + filterHistory: true, + filterInput: true, + useYCoCg: false, + boxType: Filament.View$TemporalAntiAliasingOptions$BoxType.AABB, + boxClipping: Filament.View$TemporalAntiAliasingOptions$BoxClipping.ACCURATE, + jitterPattern: Filament.View$TemporalAntiAliasingOptions$JitterPattern.HALTON_23_X16, + varianceGamma: 1.0, + preventFlickering: false, + historyReprojection: true, }; return Object.assign(options, overrides); }; diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 197a5a7acc5..17d874c3528 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -505,6 +505,7 @@ export class View { public setFogOptions(options: View$FogOptions): void; public setVignetteOptions(options: View$VignetteOptions): void; public setGuardBandOptions(options: View$GuardBandOptions): void; + public setStereoscopicOptions(options: View$StereoscopicOptions): void; public setAmbientOcclusion(ambientOcclusion: View$AmbientOcclusion): void; public getAmbientOcclusion(): View$AmbientOcclusion; public setBlendMode(mode: View$BlendMode): void; @@ -546,6 +547,8 @@ export class Engine { public createTextureFromJpeg(urlOrBuffer: BufferReference, options?: object): Texture; public createTextureFromPng(urlOrBuffer: BufferReference, options?: object): Texture; + public static getMaxStereoscopicEyes(): number; + public createIblFromKtx1(urlOrBuffer: BufferReference): IndirectLight; public createSkyFromKtx1(urlOrBuffer: BufferReference): Skybox; public createTextureFromKtx1(urlOrBuffer: BufferReference, options?: object): Texture; @@ -1406,6 +1409,10 @@ export interface View$DepthOfFieldOptions { * circle of confusion scale factor (amount of blur) */ cocScale?: number; + /** + * width/height aspect ratio of the circle of confusion (simulate anamorphic lenses) + */ + cocAspectRatio?: number; /** * maximum aperture diameter in meters (zero to disable rotation) */ @@ -1611,7 +1618,7 @@ export interface View$AmbientOcclusionOptions { } /** - * Options for Temporal Multi-Sample Anti-aliasing (MSAA) + * Options for Multi-Sample Anti-aliasing (MSAA) * @see setMultiSampleAntiAliasingOptions() */ export interface View$MultiSampleAntiAliasingOptions { @@ -1633,23 +1640,92 @@ export interface View$MultiSampleAntiAliasingOptions { customResolve?: boolean; } +export enum View$TemporalAntiAliasingOptions$BoxType { + AABB, // use an AABB neighborhood + VARIANCE, // use the variance of the neighborhood (not recommended) + AABB_VARIANCE, // use both AABB and variance +} + +export enum View$TemporalAntiAliasingOptions$BoxClipping { + ACCURATE, // Accurate box clipping + CLAMP, // clamping + NONE, // no rejections (use for debugging) +} + +export enum View$TemporalAntiAliasingOptions$JitterPattern { + RGSS_X4, + UNIFORM_HELIX_X4, + HALTON_23_X8, + HALTON_23_X16, + HALTON_23_X32, +} + /** * Options for Temporal Anti-aliasing (TAA) + * Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process + * shaders to be recompiled. These options should be changed or set during initialization. + * `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time. + * + * `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state. + * see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information. + * * @see setTemporalAntiAliasingOptions() */ export interface View$TemporalAntiAliasingOptions { /** - * reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother) + * reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother) */ filterWidth?: number; /** * history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). */ feedback?: number; + /** + * texturing lod bias (typically -1 or -2) + */ + lodBias?: number; + /** + * post-TAA sharpen, especially useful when upscaling is true. + */ + sharpness?: number; /** * enables or disables temporal anti-aliasing */ enabled?: boolean; + /** + * 4x TAA upscaling. Disables Dynamic Resolution. [BETA] + */ + upscaling?: boolean; + /** + * whether to filter the history buffer + */ + filterHistory?: boolean; + /** + * whether to apply the reconstruction filter to the input + */ + filterInput?: boolean; + /** + * whether to use the YcoCg color-space for history rejection + */ + useYCoCg?: boolean; + /** + * type of color gamut box + */ + boxType?: View$TemporalAntiAliasingOptions$BoxType; + /** + * clipping algorithm + */ + boxClipping?: View$TemporalAntiAliasingOptions$BoxClipping; + jitterPattern?: View$TemporalAntiAliasingOptions$JitterPattern; + varianceGamma?: number; + /** + * adjust the feedback dynamically to reduce flickering + */ + preventFlickering?: boolean; + /** + * whether to apply history reprojection (debug option) + */ + historyReprojection?: boolean; } /** diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 7e31c167866..87da0121452 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -369,8 +369,8 @@ using EntityVector = std::vector; register_vector("RegistryKeys"); register_vector("EntityVector"); -register_vector("AssetInstanceVector"); -register_vector("MaterialInstanceVector"); +register_vector>("AssetInstanceVector"); +register_vector>("MaterialInstanceVector"); // CORE FILAMENT CLASSES // --------------------- @@ -400,6 +400,8 @@ class_("Engine") .function("getActiveFeatureLevel", &Engine::getActiveFeatureLevel) + .class_function("getMaxStereoscopicEyes", &Engine::getMaxStereoscopicEyes) + .function("_execute", EMBIND_LAMBDA(void, (Engine* engine), { EM_ASM_INT({ const handle = window.filament_contextHandle; @@ -654,6 +656,7 @@ class_("View") .function("_setFogOptions", &View::setFogOptions) .function("_setVignetteOptions", &View::setVignetteOptions) .function("_setGuardBandOptions", &View::setGuardBandOptions) + .function("_setStereoscopicOptions", &View::setStereoscopicOptions) .function("setAmbientOcclusion", &View::setAmbientOcclusion) .function("getAmbientOcclusion", &View::getAmbientOcclusion) .function("setAntiAliasing", &View::setAntiAliasing) @@ -688,6 +691,7 @@ class_("Scene") .function("getSkybox", &Scene::getSkybox, allow_raw_pointers()) .function("setIndirectLight", &Scene::setIndirectLight, allow_raw_pointers()) .function("getIndirectLight", &Scene::getIndirectLight, allow_raw_pointers()) + .function("getEntityCount", &Scene::getEntityCount) .function("getRenderableCount", &Scene::getRenderableCount) .function("getLightCount", &Scene::getLightCount); @@ -934,6 +938,10 @@ class_("RenderableManager$Builder") size_t count), { return &builder->geometry(index, type, vertices, indices, offset, minIndex, maxIndex, count); }) + .BUILDER_FUNCTION("geometryType", RenderableBuilder, (RenderableBuilder* builder, + RenderableManager::Builder::GeometryType type), { + return &builder->geometryType(type); }) + .BUILDER_FUNCTION("material", RenderableBuilder, (RenderableBuilder* builder, size_t index, MaterialInstance* mi), { return &builder->material(index, mi); }) diff --git a/web/filament-js/jsbindings_generated.cpp b/web/filament-js/jsbindings_generated.cpp index db57b141a53..07fd54e786b 100644 --- a/web/filament-js/jsbindings_generated.cpp +++ b/web/filament-js/jsbindings_generated.cpp @@ -58,6 +58,7 @@ value_object("View$FogOptions") value_object("View$DepthOfFieldOptions") .field("cocScale", &View::DepthOfFieldOptions::cocScale) + .field("cocAspectRatio", &View::DepthOfFieldOptions::cocAspectRatio) .field("maxApertureDiameter", &View::DepthOfFieldOptions::maxApertureDiameter) .field("enabled", &View::DepthOfFieldOptions::enabled) .field("filter", &View::DepthOfFieldOptions::filter) @@ -119,7 +120,19 @@ value_object("View$MultiSampleAntiAliasing value_object("View$TemporalAntiAliasingOptions") .field("filterWidth", &View::TemporalAntiAliasingOptions::filterWidth) .field("feedback", &View::TemporalAntiAliasingOptions::feedback) + .field("lodBias", &View::TemporalAntiAliasingOptions::lodBias) + .field("sharpness", &View::TemporalAntiAliasingOptions::sharpness) .field("enabled", &View::TemporalAntiAliasingOptions::enabled) + .field("upscaling", &View::TemporalAntiAliasingOptions::upscaling) + .field("filterHistory", &View::TemporalAntiAliasingOptions::filterHistory) + .field("filterInput", &View::TemporalAntiAliasingOptions::filterInput) + .field("useYCoCg", &View::TemporalAntiAliasingOptions::useYCoCg) + .field("boxType", &View::TemporalAntiAliasingOptions::boxType) + .field("boxClipping", &View::TemporalAntiAliasingOptions::boxClipping) + .field("jitterPattern", &View::TemporalAntiAliasingOptions::jitterPattern) + .field("varianceGamma", &View::TemporalAntiAliasingOptions::varianceGamma) + .field("preventFlickering", &View::TemporalAntiAliasingOptions::preventFlickering) + .field("historyReprojection", &View::TemporalAntiAliasingOptions::historyReprojection) ; value_object("View$ScreenSpaceReflectionsOptions") diff --git a/web/filament-js/jsenums.cpp b/web/filament-js/jsenums.cpp index ec68142a0d8..5c9b90e31c8 100644 --- a/web/filament-js/jsenums.cpp +++ b/web/filament-js/jsenums.cpp @@ -274,6 +274,8 @@ enum_("Texture$Usage") // aka backend::TextureUsage .value("STENCIL_ATTACHMENT", Texture::Usage::STENCIL_ATTACHMENT) .value("UPLOADABLE", Texture::Usage::UPLOADABLE) .value("SAMPLEABLE", Texture::Usage::SAMPLEABLE) + .value("BLIT_SRC", Texture::Usage::BLIT_SRC) + .value("BLIT_DST", Texture::Usage::BLIT_DST) .value("SUBPASS_INPUT", Texture::Usage::SUBPASS_INPUT); enum_("Texture$CubemapFace") // aka backend::TextureCubemapFace diff --git a/web/filament-js/jsenums_generated.cpp b/web/filament-js/jsenums_generated.cpp index ec3f140801b..d729b59c3cc 100644 --- a/web/filament-js/jsenums_generated.cpp +++ b/web/filament-js/jsenums_generated.cpp @@ -33,6 +33,26 @@ enum_("View$DepthOfFieldOptions$Filter") .value("MEDIAN", View::DepthOfFieldOptions::Filter::MEDIAN) ; +enum_("View$TemporalAntiAliasingOptions$BoxType") + .value("AABB", View::TemporalAntiAliasingOptions::BoxType::AABB) + .value("VARIANCE", View::TemporalAntiAliasingOptions::BoxType::VARIANCE) + .value("AABB_VARIANCE", View::TemporalAntiAliasingOptions::BoxType::AABB_VARIANCE) + ; + +enum_("View$TemporalAntiAliasingOptions$BoxClipping") + .value("ACCURATE", View::TemporalAntiAliasingOptions::BoxClipping::ACCURATE) + .value("CLAMP", View::TemporalAntiAliasingOptions::BoxClipping::CLAMP) + .value("NONE", View::TemporalAntiAliasingOptions::BoxClipping::NONE) + ; + +enum_("View$TemporalAntiAliasingOptions$JitterPattern") + .value("RGSS_X4", View::TemporalAntiAliasingOptions::JitterPattern::RGSS_X4) + .value("UNIFORM_HELIX_X4", View::TemporalAntiAliasingOptions::JitterPattern::UNIFORM_HELIX_X4) + .value("HALTON_23_X8", View::TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X8) + .value("HALTON_23_X16", View::TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X16) + .value("HALTON_23_X32", View::TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X32) + ; + enum_("View$AntiAliasing") .value("NONE", View::AntiAliasing::NONE) .value("FXAA", View::AntiAliasing::FXAA) diff --git a/web/filament-js/package.json b/web/filament-js/package.json index b832762146e..b114a397027 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.44.0", + "version": "1.51.3", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js",