diff --git a/BUILDING.md b/BUILDING.md index 7146b751b00..28e6861caa4 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -56,9 +56,11 @@ To trigger both incremental debug and release builds: ./build.sh debug release ``` +If build fails for some reasons, it may leave the `out/` directory in a broken state. You can +force a clean build by adding the `-c` flag in that case. + To install the libraries and executables in `out/debug/` and `out/release/`, add the `-i` flag. -You can force a clean build by adding the `-c` flag. The script offers more features described -by executing `build.sh -h`. +The script offers more features described by executing `build.sh -h`. ### Filament-specific CMake Options @@ -172,12 +174,12 @@ See [ios/samples/README.md](./ios/samples/README.md) for more information. ### Windows -#### Building on Windows with Visual Studio 2019 +#### Building on Windows with Visual Studio 2019 or later Install the following components: -- [Visual Studio 2019](https://www.visualstudio.com/downloads) -- [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) +- [Visual Studio 2019 or later](https://www.visualstudio.com/downloads) +- [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/) - [Python 3.7](https://www.python.org/ftp/python/3.7.0/python-3.7.0.exe) - [CMake 3.14 or later](https://github.com/Kitware/CMake/releases/download/v3.14.7/cmake-3.14.7-win64-x64.msi) diff --git a/README.md b/README.md index 3da3ee8f712..1c9b9c01057 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.45.0' + implementation 'com.google.android.filament:filament-android:1.45.1' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.45.0' +pod 'Filament', '~> 1.45.1' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 8bb4209a27d..453d4f4c169 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,12 +7,20 @@ 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.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] + ## v1.45.0 - materials: fix alpha masked materials when MSAA is turned on [⚠️ **Recompile materials**] - materials: better support materials with custom depth [**Recompile Materials**] - engine: fade shadows at shadowFar distance instead of hard cutoff [⚠️ **New Material Version**] -- engine: Add support for stencil buffer when post-processing is disabled (Metal backend only). ## v1.44.0 diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index e530e7bb55b..e5bb4e95fa7 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -25,15 +25,8 @@ using namespace filament; using namespace utils; -extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_Engine_nCreateEngine(JNIEnv*, jclass, jlong backend, - jlong sharedContext) { - return (jlong) Engine::create((Engine::Backend) backend, nullptr, (void*) sharedContext); -} - extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv*, jclass, - jlong nativeEngine) { +Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv*, jclass, jlong nativeEngine) { Engine* engine = (Engine*) nativeEngine; Engine::destroy(&engine); } @@ -454,4 +447,50 @@ Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass, jlong nativeEngine) { Engine* engine = (Engine*) nativeEngine; return (jint)engine->getActiveFeatureLevel(); -} \ No newline at end of file +} + +extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nCreateBuilder(JNIEnv*, + jclass) { + Engine::Builder* builder = new Engine::Builder{}; + return (jlong) builder; +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nDestroyBuilder(JNIEnv*, + jclass, jlong nativeBuilder) { + Engine::Builder* builder = (Engine::Builder*) nativeBuilder; + delete builder; +} + +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderBackend( + JNIEnv*, jclass, jlong nativeBuilder, jlong backend) { + Engine::Builder* builder = (Engine::Builder*) nativeBuilder; + builder->backend((Engine::Backend) backend); +} + +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) { + Engine::Builder* builder = (Engine::Builder*) nativeBuilder; + Engine::Config config = { + .commandBufferSizeMB = (uint32_t) commandBufferSizeMB, + .perRenderPassArenaSizeMB = (uint32_t) perRenderPassArenaSizeMB, + .driverHandleArenaSizeMB = (uint32_t) driverHandleArenaSizeMB, + .minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB, + .perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB, + .jobSystemThreadCount = (uint32_t) jobSystemThreadCount, + }; + builder->config(&config); +} + +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 jlong JNICALL +Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) { + Engine::Builder* builder = (Engine::Builder*) nativeBuilder; + return (jlong) builder->build(); +} diff --git a/android/filament-android/src/main/cpp/Scene.cpp b/android/filament-android/src/main/cpp/Scene.cpp index 25e6be09ba4..b1d3d003489 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) { diff --git a/android/filament-android/src/main/cpp/ToneMapper.cpp b/android/filament-android/src/main/cpp/ToneMapper.cpp index ea040538cd7..21c7e7ab24d 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_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) { + return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look)); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_ToneMapper_nCreateGenericToneMapper(JNIEnv*, jclass, jfloat contrast, jfloat midGrayIn, jfloat midGrayOut, jfloat hdrMax) { 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 e6bc4d92430..3423e87d056 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 @@ -154,6 +154,184 @@ public enum FeatureLevel { FEATURE_LEVEL_2 }; + /** + * Constructs Engine objects using a builder pattern. + */ + public static class Builder { + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + private final BuilderFinalizer mFinalizer; + private final long mNativeBuilder; + + public Builder() { + mNativeBuilder = nCreateBuilder(); + mFinalizer = new BuilderFinalizer(mNativeBuilder); + } + + /** + * Sets the {@link Backend} for the Engine. + * + * @param backend Driver backend to use + * @return A reference to this Builder for chaining calls. + */ + public Builder backend(Backend backend) { + nSetBuilderBackend(mNativeBuilder, backend.ordinal()); + return this; + } + + /** + * Sets a sharedContext for the Engine. + * + * @param sharedContext A platform-dependant OpenGL context used as a shared context + * when creating filament's internal context. On Android this parameter + * must be an instance of {@link android.opengl.EGLContext}. + * @return A reference to this Builder for chaining calls. + */ + public Builder sharedContext(Object sharedContext) { + if (Platform.get().validateSharedContext(sharedContext)) { + nSetBuilderSharedContext(mNativeBuilder, + Platform.get().getSharedContextNativeHandle(sharedContext)); + return this; + } + throw new IllegalArgumentException("Invalid shared context " + sharedContext); + } + + /** + * Configure the Engine with custom parameters. + * + * @param config A {@link Config} object + * @return A reference to this Builder for chaining calls. + */ + public Builder config(Config config) { + nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB, + config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB, + config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB, + config.jobSystemThreadCount); + return this; + } + + /** + * Creates an instance of Engine + * + * @return A newly created Engine, or null if the GPU driver couldn't + * be initialized, for instance if it doesn't support the right version of OpenGL or + * OpenGL ES. + * + * @exception IllegalStateException can be thrown if there isn't enough memory to + * allocate the command buffer. + */ + public Engine build() { + long nativeEngine = nBuilderBuild(mNativeBuilder); + if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine"); + return new Engine(nativeEngine); + } + + private static class BuilderFinalizer { + private final long mNativeObject; + + BuilderFinalizer(long nativeObject) { + mNativeObject = nativeObject; + } + + @Override + public void finalize() { + try { + super.finalize(); + } catch (Throwable t) { // Ignore + } finally { + nDestroyBuilder(mNativeObject); + } + } + } + } + + /** + * Parameters for customizing the initialization of {@link Engine}. + */ + public static class Config { + + // #defines in Engine.h + private static final long FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB = 3; + private static final long FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB = 2; + private static final long FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB = 1; + private static final long FILAMENT_COMMAND_BUFFER_SIZE_IN_MB = + FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB * 3; + + /** + * Size in MiB of the low-level command buffer arena. + * + * Each new command buffer is allocated from here. If this buffer is too small the program + * might terminate or rendering errors might occur. + * + * This is typically set to minCommandBufferSizeMB * 3, so that up to 3 frames can be + * batched-up at once. + * + * This value affects the application's memory usage. + */ + public long commandBufferSizeMB = FILAMENT_COMMAND_BUFFER_SIZE_IN_MB; + + /** + * Size in MiB of the per-frame data arena. + * + * This is the main arena used for allocations when preparing a frame. + * e.g.: Froxel data and high-level commands are allocated from this arena. + * + * If this size is too small, the program will abort on debug builds and have undefined + * behavior otherwise. + * + * This value affects the application's memory usage. + */ + public long perRenderPassArenaSizeMB = FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB; + + /** + * Size in MiB of the backend's handle arena. + * + * Backends will fallback to slower heap-based allocations when running out of space and + * log this condition. + * + * If 0, then the default value for the given platform is used + * + * This value affects the application's memory usage. + */ + public long driverHandleArenaSizeMB = 0; + + /** + * Minimum size in MiB of a low-level command buffer. + * + * This is how much space is guaranteed to be available for low-level commands when a new + * buffer is allocated. If this is too small, the engine might have to stall to wait for + * more space to become available, this situation is logged. + * + * This value does not affect the application's memory usage. + */ + public long minCommandBufferSizeMB = FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB; + + /** + * Size in MiB of the per-frame high level command buffer. + * + * This buffer is related to the number of draw calls achievable within a frame, if it is + * too small, the program will abort on debug builds and have undefined behavior otherwise. + * + * It is allocated from the 'per-render-pass arena' above. Make sure that at least 1 MiB is + * left in the per-render-pass arena when deciding the size of this buffer. + * + * This value does not affect the application's memory usage. + */ + public long perFrameCommandsSizeMB = FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB; + + /** + * Number of threads to use in Engine's JobSystem. + * + * Engine uses a utils::JobSystem to carry out paralleization of Engine workloads. This + * value sets the number of threads allocated for JobSystem. Configuring this value can be + * helpful in CPU-constrained environments where too many threads can cause contention of + * CPU and reduce performance. + * + * The default value is 0, which implies that the Engine will use a heuristic to determine + * the number of threads to use. + */ + public long jobSystemThreadCount = 0; + } + private Engine(long nativeEngine) { mNativeObject = nativeEngine; mTransformManager = new TransformManager(nGetTransformManager(nativeEngine)); @@ -177,9 +355,7 @@ private Engine(long nativeEngine) { */ @NonNull public static Engine create() { - long nativeEngine = nCreateEngine(0, 0); - if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine"); - return new Engine(nativeEngine); + return new Builder().build(); } /** @@ -199,9 +375,9 @@ public static Engine create() { */ @NonNull public static Engine create(@NonNull Backend backend) { - long nativeEngine = nCreateEngine(backend.ordinal(), 0); - if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine"); - return new Engine(nativeEngine); + return new Builder() + .backend(backend) + .build(); } /** @@ -223,13 +399,9 @@ public static Engine create(@NonNull Backend backend) { */ @NonNull public static Engine create(@NonNull Object sharedContext) { - if (Platform.get().validateSharedContext(sharedContext)) { - long nativeEngine = nCreateEngine(0, - Platform.get().getSharedContextNativeHandle(sharedContext)); - if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine"); - return new Engine(nativeEngine); - } - throw new IllegalArgumentException("Invalid shared context " + sharedContext); + return new Builder() + .sharedContext(sharedContext) + .build(); } /** @@ -914,7 +1086,6 @@ private static void assertDestroy(boolean success) { } } - private static native long nCreateEngine(long backend, long sharedContext); private static native void nDestroyEngine(long nativeEngine); private static native long nGetBackend(long nativeEngine); private static native long nCreateSwapChain(long nativeEngine, Object nativeWindow, long flags); @@ -971,4 +1142,13 @@ private static void assertDestroy(boolean success) { private static native int nGetSupportedFeatureLevel(long nativeEngine); private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal); private static native int nGetActiveFeatureLevel(long nativeEngine); + + private static native long nCreateBuilder(); + private static native void nDestroyBuilder(long nativeBuilder); + 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); + private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext); + private static native long nBuilderBuild(long nativeBuilder); } 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..fd425fbeaac 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 @@ -146,18 +146,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()); @@ -189,6 +200,7 @@ 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); 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 15800562e3e..58ffc501ec1 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 @@ -100,6 +100,45 @@ public Filmic() { } } + /** + * AgX tone mapping operator. + */ + public static class Agx extends ToneMapper { + + public enum AgxLook { + /** + * Base contrast with no look applied + */ + NONE, + + /** + * A punchy and more chroma laden look for sRGB displays + */ + PUNCHY, + + /** + * A golden tinted, slightly washed look for BT.1886 displays + */ + GOLDEN + } + + /** + * Builds a new AgX tone mapper with no look applied. + */ + public Agx() { + this(AgxLook.NONE); + } + + /** + * Builds a new AgX tone mapper. + * + * @param look: an optional creative adjustment to contrast and saturation + */ + public Agx(AgxLook look) { + super(nCreateAgxToneMapper(look.ordinal())); + } + } + /** * Generic tone mapping operator that gives control over the tone mapping * curve. This operator can be used to control the aesthetics of the final @@ -194,6 +233,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 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 6c7b794f304..91622d26c41 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 @@ -1930,6 +1930,7 @@ public enum ShadowType { * PCF with soft shadows and contact hardening */ PCSS, + PCFd, } /** diff --git a/android/gradle.properties b/android/gradle.properties index 61ffc823bb5..0fdca3754ec 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.45.0 +VERSION_NAME=1.45.1 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/docs/viewer/filament-viewer.js b/docs/viewer/filament-viewer.js index 01439952f48..652d55d8ae4 100644 --- a/docs/viewer/filament-viewer.js +++ b/docs/viewer/filament-viewer.js @@ -15,7 +15,7 @@ */ // If you are bundling this with rollup, webpack, or esbuild, the following URL should be trimmed. -import { LitElement, html, css } from "https://unpkg.com/lit?module"; +import { LitElement, html, css } from "https://unpkg.com/lit@2.8.0?module"; // This little utility checks if the Filament module is ready for action. // If so, it immediately calls the given function. If not, it asks the Filament @@ -287,12 +287,12 @@ class FilamentViewer extends LitElement { // Dropping a glb file is simple because there are no external resources. if (this.srcBlob && this.srcBlob.name.endsWith(".glb")) { this.srcBlob.arrayBuffer().then(buffer => { - this.asset = this.loader.createAssetFromBinary(new Uint8Array(buffer)); + this.asset = this.loader.createAsset(new Uint8Array(buffer)); const aabb = this.asset.getBoundingBox(); this.assetRoot = this.asset.getRoot(); this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset); this.asset.loadResources(); - this.animator = this.asset.getAnimator(); + this.animator = this.asset.getInstance().getAnimator(); this.animationStartTime = Date.now(); this._updateOverlay(); }); @@ -304,8 +304,6 @@ class FilamentViewer extends LitElement { const config = { normalizeSkinningWeights: true, - recomputeBoundingBoxes: false, - ignoreBindTransform: false, asyncInterval: 30 }; @@ -320,22 +318,20 @@ class FilamentViewer extends LitElement { resourceLoader.delete(); stbProvider.delete(); ktx2Provider.delete(); - this.animator = this.asset.getAnimator(); + this.animator = this.asset.getInstance().getAnimator(); this.animationStartTime = Date.now(); } }, config.asyncInterval); }; this.srcBlob.arrayBuffer().then(buffer => { - this.asset = this.loader.createAssetFromJson(new Uint8Array(buffer)); + this.asset = this.loader.createAsset(new Uint8Array(buffer)); const aabb = this.asset.getBoundingBox(); this.assetRoot = this.asset.getRoot(); this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset); const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine, - config.normalizeSkinningWeights, - config.recomputeBoundingBoxes, - config.ignoreBindTransform); + config.normalizeSkinningWeights); const stbProvider = new Filament.gltfio$StbProvider(this.engine); const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine); @@ -367,12 +363,7 @@ class FilamentViewer extends LitElement { return response.arrayBuffer(); }).then(arrayBuffer => { const modelData = new Uint8Array(arrayBuffer); - if (this.src.endsWith(".glb")) { - this.asset = this.loader.createAssetFromBinary(modelData); - } else { - this.asset = this.loader.createAssetFromJson(modelData); - } - + this.asset = this.loader.createAsset(modelData); const aabb = this.asset.getBoundingBox(); this.assetRoot = this.asset.getRoot(); this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset); @@ -380,7 +371,7 @@ class FilamentViewer extends LitElement { const basePath = '' + new URL(this.src, document.location); this.asset.loadResources(() => { - this.animator = this.asset.getAnimator(); + this.animator = this.asset.getInstance().getAnimator(); this.animationStartTime = Date.now(); this._applyMaterialVariant(); }, null, basePath); @@ -441,14 +432,15 @@ class FilamentViewer extends LitElement { if (!this.hasAttribute("materialVariant")) { return; } - const names = this.asset.getMaterialVariantNames(); + const instance = this.asset.getInstance(); + const names = instance.getMaterialVariantNames(); const index = this.materialVariant; if (index < 0 || index >= names.length) { console.error(`Material variant ${index} does not exist in this asset.`); return; } console.info(this.src, `Applying material variant: ${names[index]}`); - this.asset.applyMaterialVariant(index); + instance.applyMaterialVariant(index); } } diff --git a/docs/viewer/index.html b/docs/viewer/index.html index 8d1ca5a7609..2bbc6b187d3 100644 --- a/docs/viewer/index.html +++ b/docs/viewer/index.html @@ -43,7 +43,7 @@

- + diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 125a2a780f2..e088618b7b8 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -133,6 +133,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 diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index a84a8ba01fe..dac1e37630c 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -114,6 +114,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 @@ -123,9 +124,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 hasBlobFunc() const noexcept; + 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 { + return hasInsertBlobFunc() || hasRetrieveBlobFunc(); + } /** * To insert a new binary value into the cache and associate it with a given diff --git a/filament/backend/include/backend/platforms/PlatformEGL.h b/filament/backend/include/backend/platforms/PlatformEGL.h index 79400540063..d168fdd9ba8 100644 --- a/filament/backend/include/backend/platforms/PlatformEGL.h +++ b/filament/backend/include/backend/platforms/PlatformEGL.h @@ -42,6 +42,9 @@ class PlatformEGL : public OpenGLPlatform { void createContext(bool shared) override; void releaseContext() noexcept override; + // Return true if we're on an OpenGL platform (as opposed to OpenGL ES). false by default. + virtual bool isOpenGL() const noexcept; + protected: // -------------------------------------------------------------------------------------------- @@ -126,6 +129,7 @@ class PlatformEGL : public OpenGLPlatform { EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE; EGLSurface mCurrentReadSurface = EGL_NO_SURFACE; EGLSurface mEGLDummySurface = EGL_NO_SURFACE; + // mEGLConfig is valid only if ext.egl.KHR_no_config_context is false EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR; Config mContextAttribs; std::vector mAdditionalContexts; @@ -146,8 +150,8 @@ class PlatformEGL : public OpenGLPlatform { void initializeGlExtensions() noexcept; -private: - EGLConfig findSwapChainConfig(uint64_t flags) const; +protected: + EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; }; } // namespace filament::backend 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/VulkanPlatform.h b/filament/backend/include/backend/platforms/VulkanPlatform.h index 87ffc44c5d5..c940567a51e 100644 --- a/filament/backend/include/backend/platforms/VulkanPlatform.h +++ b/filament/backend/include/backend/platforms/VulkanPlatform.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -89,36 +90,44 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation #define HandleAllocatorVK HandleAllocator<16, 64, 880> -#define HandleAllocatorMTL HandleAllocator<16, 64, 576> +#define HandleAllocatorMTL HandleAllocator<16, 64, 584> namespace filament::backend { @@ -239,14 +239,16 @@ class HandleAllocator { } }; - +// 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 @@ -256,6 +258,7 @@ class HandleAllocator { HandleBase::HandleId allocateHandle() noexcept { if constexpr (SIZE <= P0) { return allocateHandleInPool(); } if constexpr (SIZE <= P1) { return allocateHandleInPool(); } + static_assert(SIZE <= P2); return allocateHandleInPool(); } @@ -266,6 +269,7 @@ class HandleAllocator { } else if constexpr (SIZE <= P1) { deallocateHandleFromPool(id); } else { + static_assert(SIZE <= P2); deallocateHandleFromPool(id); } } diff --git a/filament/backend/src/Platform.cpp b/filament/backend/src/Platform.cpp index ece36477bab..db2fd0eafdf 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) { diff --git a/filament/backend/src/metal/MetalContext.h b/filament/backend/src/metal/MetalContext.h index 5a26e2600aa..f679065583f 100644 --- a/filament/backend/src/metal/MetalContext.h +++ b/filament/backend/src/metal/MetalContext.h @@ -18,6 +18,7 @@ #define TNT_METALCONTEXT_H #include "MetalResourceTracker.h" +#include "MetalShaderCompiler.h" #include "MetalState.h" #include @@ -149,6 +150,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..1daec348f54 100644 --- a/filament/backend/src/metal/MetalDriver.h +++ b/filament/backend/src/metal/MetalDriver.h @@ -34,11 +34,11 @@ 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 diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 8933430fc2a..1666edf8cf4 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -144,6 +144,9 @@ mContext->eventListener = [[MTLSharedEventListener alloc] initWithDispatchQueue:queue]; } + mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this); + 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); @@ -158,6 +161,7 @@ delete mContext->bufferPool; delete mContext->blitter; delete mContext->timerQueryImpl; + delete mContext->shaderCompiler; delete mContext; } @@ -319,7 +323,7 @@ } 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) { @@ -584,6 +588,7 @@ MetalExternalImage::shutdown(*mContext); mContext->blitter->shutdown(); + mContext->shaderCompiler->terminate(); } ShaderModel MetalDriver::getShaderModel() const noexcept { @@ -719,7 +724,7 @@ } bool MetalDriver::isParallelShaderCompileSupported() { - return false; + return true; } bool MetalDriver::isWorkaroundNeeded(Workaround workaround) { @@ -957,7 +962,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); } } @@ -1473,14 +1478,19 @@ 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."); + ASSERT_PRECONDITION(bool(functions), "Attempting to draw with an invalid Metal program."); + + auto [fragment, vertex] = functions.getRasterFunctions(); // Pipeline state MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid }; @@ -1503,8 +1513,8 @@ assert_invariant(isMetalFormatStencil(stencilPixelFormat)); } MetalPipelineState pipelineState { - .vertexFunction = program->vertexFunction, - .fragmentFunction = program->fragmentFunction, + .vertexFunction = vertex, + .fragmentFunction = fragment, .vertexDescription = primitive->vertexDescription, .colorAttachmentPixelFormat = { colorPixelFormat[0], @@ -1649,7 +1659,7 @@ if (!samplerGroup) { continue; } - const auto& stageFlags = program->samplerGroupInfo[s].stageFlags; + const auto& stageFlags = program->getSamplerGroupInfo()[s].stageFlags; if (stageFlags == ShaderStageFlags::NONE) { continue; } @@ -1676,26 +1686,23 @@ // Bind the user vertex buffers. - MetalBuffer* buffers[MAX_VERTEX_BUFFER_COUNT] = {}; + 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; - } - - assert_invariant(vb->buffers[attribute.buffer]); - buffers[bufferIndex] = vb->buffers[attribute.buffer]; - vertexBufferOffsets[bufferIndex] = attribute.offset; - bufferIndex++; + for (auto m : primitive->bufferMapping) { + assert_invariant( + m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START && + m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT); + size_t 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. @@ -1722,21 +1729,26 @@ 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]; diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index b4db3131eec..fdc4a5e6a98 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -160,6 +160,7 @@ struct MetalIndexBuffer : public HwIndexBuffer { }; struct MetalRenderPrimitive : public HwRenderPrimitive { + MetalRenderPrimitive(); void setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer); // The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak". // The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive. @@ -169,18 +170,35 @@ struct MetalRenderPrimitive : public HwRenderPrimitive { // 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 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 { diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index c43338c4487..b866053e167 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -299,6 +299,9 @@ void presentDrawable(bool presentFrame, void* user) { uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount), buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { } +MetalRenderPrimitive::MetalRenderPrimitive() + : bufferMapping(utils::FixedCapacityVector::with_capacity(MAX_VERTEX_BUFFER_COUNT)) {} + void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer) { this->vertexBuffer = vertexBuffer; @@ -306,118 +309,94 @@ void presentDrawable(bool presentFrame, void* user) { const size_t attributeCount = vertexBuffer->attributes.size(); + auto& mapping = bufferMapping; + mapping.clear(); vertexDescription = {}; - // Each attribute gets its own vertex buffer, starting at logical buffer 1. - uint32_t bufferIndex = 1; + // Set the layout for the zero buffer, which unused attributes are mapped to. + vertexDescription.layouts[ZERO_VERTEX_BUFFER_LOGICAL_INDEX] = { + .step = MTLVertexStepFunctionConstant, .stride = 16 + }; + + // 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; + } + }; + 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; - // 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 + .format = getMetalFormat( + attribute.type, attribute.flags & Attribute::FLAG_NORMALIZED), + .buffer = uint32_t(bufferArgumentIndex), + .offset = attribute.offset }; - vertexDescription.layouts[bufferIndex] = { - .step = MTLVertexStepFunctionPerVertex, - .stride = attribute.stride - }; - - 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 }; +MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept + : HwProgram(program.getName()), mContext(context) { - 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; - } + // Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to + // the appropriate stage(s). + samplerGroupInfo = program.getSamplerGroupInfo(); - 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; - } + mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program)); + assert_invariant(mToken); +} - 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); - } +const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctions() { + initialize(); + return mFunctionBundle; +} - 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; +void MetalProgram::initialize() { + if (!mToken) { + return; } - - 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); - - // All stages of the program have compiled successfully, this is a valid program. - isValid = true; - - // Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to - // the appropriate stage(s). - samplerGroupInfo = program.getSamplerGroupInfo(); + mFunctionBundle = mContext.shaderCompiler->getProgram(mToken); + assert_invariant(!mToken); } MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, diff --git a/filament/backend/src/metal/MetalShaderCompiler.h b/filament/backend/src/metal/MetalShaderCompiler.h new file mode 100644 index 00000000000..c3b3d4e1b8c --- /dev/null +++ b/filament/backend/src/metal/MetalShaderCompiler.h @@ -0,0 +1,110 @@ +/* + * 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 + +namespace filament::backend { + +class MetalDriver; + +class MetalShaderCompiler { + struct MetalProgramToken; + +public: + class MetalFunctionBundle { + public: + MetalFunctionBundle() = default; + MetalFunctionBundle(id fragment, id vertex) + : functions{fragment, vertex} { + assert_invariant(fragment && vertex); + assert_invariant(fragment.functionType == MTLFunctionTypeFragment); + assert_invariant(vertex.functionType == MTLFunctionTypeVertex); + } + explicit MetalFunctionBundle(id compute) : functions{compute, nil} { + assert_invariant(compute); + assert_invariant(compute.functionType == MTLFunctionTypeKernel); + } + + std::pair, id> getRasterFunctions() const noexcept { + assert_invariant(functions[0].functionType == MTLFunctionTypeFragment); + assert_invariant(functions[1].functionType == MTLFunctionTypeVertex); + return {functions[0], functions[1]}; + } + + id getComputeFunction() const noexcept { + assert_invariant(functions[0].functionType == MTLFunctionTypeKernel); + return functions[0]; + } + + explicit operator bool() const { return functions[0] != nil; } + + private: + // Can hold two functions, either: + // - fragment and vertex (for rasterization pipelines) + // - compute (for compute pipelines) + id functions[2] = {nil, nil}; + }; + + using program_token_t = std::shared_ptr; + + explicit MetalShaderCompiler(id device, MetalDriver& driver); + + 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; + + // Creates a program asynchronously + program_token_t createProgram(utils::CString const& name, Program&& program); + + // Returns the functions, blocking if necessary. The Token is destroyed and becomes invalid. + MetalFunctionBundle getProgram(program_token_t& token); + + // Destroys a valid token and all associated resources. Used to "cancel" a program compilation. + static void terminate(program_token_t& token); + + void notifyWhenAllProgramsAreReady( + CallbackHandler* handler, CallbackHandler::Callback callback, void* user); + +private: + static MetalFunctionBundle compileProgram(const Program& program, id device); + + CompilerThreadPool mCompilerThreadPool; + id mDevice; + CallbackManager mCallbackManager; +}; + +} // 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..e798d1b5ae3 --- /dev/null +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -0,0 +1,222 @@ +/* + * 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) + : mDevice(device), + mCallbackManager(driver) { + +} + +void MetalShaderCompiler::init() noexcept { + const uint32_t poolSize = 2; + mCompilerThreadPool.init(poolSize, []() {}, []() {}); +} + +void MetalShaderCompiler::terminate() noexcept { + mCompilerThreadPool.terminate(); + mCallbackManager.terminate(); +} + +/* 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]; + 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 {}; + } + + 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 {fragmentFunction, vertexFunction}; + } + + if (isComputeProgram) { + return MetalFunctionBundle{computeFunction}; + } + + return {}; +} + +MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram( + CString const& name, Program&& program) { + auto token = std::make_shared(*this); + + token->handle = mCallbackManager.get(); + + CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); + mCompilerThreadPool.queue(priorityQueue, token, + [this, name, device = mDevice, program = std::move(program), token]() { + int sleepTime = atoi(name.c_str()); + sleep(sleepTime); + + MetalFunctionBundle compiledProgram = compileProgram(program, device); + + token->set(compiledProgram); + mCallbackManager.put(token->handle); + }); + + return token; +} + +MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::getProgram(program_token_t& token) { + assert_invariant(token); + + if (!token->isReady()) { + auto job = mCompilerThreadPool.dequeue(token); + if (job) { + job(); + } + } + + MetalShaderCompiler::MetalFunctionBundle program = token->get(); + + token = nullptr; + + return program; +} + +/* static */ void MetalShaderCompiler::terminate(program_token_t& token) { + assert_invariant(token); + + auto job = token->compiler.mCompilerThreadPool.dequeue(token); + if (!job) { + // The job is being executed right now (or has already executed). + token->wait(); + } else { + // The job has not executed yet. + token->compiler.mCallbackManager.put(token->handle); + } + + token.reset(); +} + +void MetalShaderCompiler::notifyWhenAllProgramsAreReady( + CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { + mCallbackManager.setCallback(handler, callback, user); +} + +} // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLBlobCache.cpp b/filament/backend/src/opengl/OpenGLBlobCache.cpp index 4f65c605867..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,46 +90,36 @@ 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 = 0; + 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 }; - if (UTILS_LIKELY(blob)) { + } + 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; - platform.insertBlob(key.data(), key.size(), blob.get(), size); - } + glGetProgramBinary(program, programBinarySize, + &programBinarySize, &format, blob->data); } - } - } -#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 }; - if (UTILS_LIKELY(blob)) { + GLenum const error = glGetError(); + if (error == GL_NO_ERROR) { blob->format = format; - memcpy(blob->data, data, programBinarySize); platform.insertBlob(key.data(), key.size(), blob.get(), size); } } } +#endif } } // 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..bf4c7e1f133 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -99,38 +99,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 +146,18 @@ 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 diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index ff8b29cbd54..4ba559c78c3 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -153,14 +153,15 @@ class OpenGLContext { // 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 = {}; diff --git a/filament/backend/src/opengl/ShaderCompilerService.cpp b/filament/backend/src/opengl/ShaderCompilerService.cpp index 4d49cca9bfe..9ab30a9bf10 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.cpp +++ b/filament/backend/src/opengl/ShaderCompilerService.cpp @@ -140,6 +140,7 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver) : mDriver(driver), + mBlobCache(driver.getContext()), mCallbackManager(driver), KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) { } @@ -219,7 +220,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; } @@ -264,7 +265,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( // caching must be the last thing we do 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); } }); @@ -317,7 +318,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; @@ -431,7 +432,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. diff --git a/filament/backend/src/opengl/ShaderCompilerService.h b/filament/backend/src/opengl/ShaderCompilerService.h index bbce6a5c23f..0ff9e493cb0 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 @@ -95,6 +96,7 @@ class ShaderCompilerService { private: OpenGLDriver& mDriver; + OpenGLBlobCache mBlobCache; CallbackManager mCallbackManager; CompilerThreadPool mCompilerThreadPool; diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp index 60652b54156..76abd8d5540 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -97,13 +97,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; @@ -148,10 +167,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 +197,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. @@ -333,22 +361,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) { @@ -391,7 +433,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain( 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; } @@ -427,7 +469,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain( EGLConfig config = EGL_NO_CONFIG_KHR; if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) { - config = findSwapChainConfig(flags); + config = findSwapChainConfig(flags, false, true); } else { config = mEGLConfig; } @@ -569,7 +611,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; diff --git a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp index c273b66d624..b61bbeab9a7 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp @@ -185,7 +185,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/vulkan/VulkanBlitter.cpp b/filament/backend/src/vulkan/VulkanBlitter.cpp index 9f20b11ac8c..bfda73fa326 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.cpp +++ b/filament/backend/src/vulkan/VulkanBlitter.cpp @@ -237,11 +237,12 @@ void VulkanBlitter::lazyInit() noexcept { VkShaderModule vertexShader = decode(VKSHADERS_BLITDEPTHVS_DATA, VKSHADERS_BLITDEPTHVS_SIZE); VkShaderModule fragmentShader = decode(VKSHADERS_BLITDEPTHFS_DATA, VKSHADERS_BLITDEPTHFS_SIZE); - mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader); // Allocate one anonymous sampler at slot 0. - mDepthResolveProgram->samplerGroupInfo[0].samplers.reserve(1); - mDepthResolveProgram->samplerGroupInfo[0].samplers.resize(1); + 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 " @@ -359,7 +360,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V // DRAW THE TRIANGLE // ----------------- - mPipelineCache.bindProgram(*mDepthResolveProgram); + mPipelineCache.bindProgram(mDepthResolveProgram); mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); auto vkraster = mPipelineCache.getCurrentRasterState(); diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index 2dba9d3bdeb..c9a4fa2c4e7 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -20,10 +20,12 @@ #include "VulkanConstants.h" #include "VulkanImageUtility.h" #include "VulkanPipelineCache.h" +#include "VulkanUtility.h" +#include +#include #include #include -#include #include @@ -100,8 +102,8 @@ struct VulkanContext { return (uint32_t) VK_MAX_MEMORY_TYPES; } - inline VkFormat getDepthFormat() const { - return mDepthFormat; + inline VkFormatList const& getAttachmentDepthFormats() const { + return mDepthFormats; } inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept { @@ -130,7 +132,7 @@ struct VulkanContext { bool mDebugMarkersSupported = false; bool mDebugUtilsSupported = false; - VkFormat mDepthFormat; + VkFormatList mDepthFormats; // For convenience so that VulkanPlatform can initialize the private fields. friend class VulkanPlatform; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 4b8f4d5e4f7..d64b0a69e54 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -156,7 +156,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mThreadSafeResourceManager(&mResourceAllocator), mPipelineCache(&mResourceAllocator), mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache), - mReadPixels(mPlatform->getDevice()) { + mReadPixels(mPlatform->getDevice()), + mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) { #if FVK_ENABLED(FVK_DEBUG_VALIDATION) UTILS_UNUSED const PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback @@ -706,10 +707,6 @@ FenceStatus VulkanDriver::getFenceStatus(Handle fh) { // the GPU supports the given texture format with non-zero optimal tiling features. bool VulkanDriver::isTextureFormatSupported(TextureFormat format) { VkFormat vkformat = getVkFormat(format); - // We automatically use an alternative format when the client requests DEPTH24. - if (format == TextureFormat::DEPTH24) { - vkformat = mContext.getDepthFormat(); - } if (vkformat == VK_FORMAT_UNDEFINED) { return false; } @@ -737,10 +734,6 @@ bool VulkanDriver::isTextureFormatMipmappable(TextureFormat format) { bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) { VkFormat vkformat = getVkFormat(format); - // We automatically use an alternative format when the client requests DEPTH24. - if (format == TextureFormat::DEPTH24) { - vkformat = mContext.getDepthFormat(); - } if (vkformat == VK_FORMAT_UNDEFINED) { return false; } @@ -766,7 +759,7 @@ bool VulkanDriver::isAutoDepthResolveSupported() { } bool VulkanDriver::isSRGBSwapChainSupported() { - return mPlatform->isSRGBSwapChainSupported(); + return mIsSRGBSwapChainSupported; } bool VulkanDriver::isStereoSupported() { @@ -1549,7 +1542,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets(); // Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls. - mPipelineCache.bindProgram(*program); + mPipelineCache.bindProgram(program); mPipelineCache.bindRasterState(mPipelineCache.getCurrentRasterState()); mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology); mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount); @@ -1561,68 +1554,69 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {}; VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr}; - VulkanPipelineCache::UsageFlags usage; + auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex(); + VulkanPipelineCache::UsageFlags usage = program->getUsage(); UTILS_NOUNROLL - for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT; samplerGroupIdx++) { - const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx]; - const auto& samplers = samplerGroup.samplers; - if (samplers.empty()) { + for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) { + uint16_t const indexPair = bindingToSamplerIndex[binding]; + + if (indexPair == 0xffff) { + usage = VulkanPipelineCache::disableUsageFlags(binding, usage); continue; } - VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx]; + + uint16_t const samplerGroupInd = (indexPair >> 8) & 0xff; + uint16_t const samplerInd = (indexPair & 0xff); + + VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd]; if (!vksb) { + usage = VulkanPipelineCache::disableUsageFlags(binding, usage); continue; } - SamplerGroup* sb = vksb->sb.get(); - assert_invariant(sb->getSize() == samplers.size()); - size_t samplerIdx = 0; - for (auto& sampler : samplers) { - const SamplerDescriptor* boundSampler = sb->data() + samplerIdx; - samplerIdx++; + SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd; - if (UTILS_LIKELY(boundSampler->t)) { - VulkanTexture* texture = mResourceAllocator.handle_cast(boundSampler->t); - VkImageViewType const expectedType = texture->getViewType(); + if (UTILS_UNLIKELY(!boundSampler->t)) { + usage = VulkanPipelineCache::disableUsageFlags(binding, usage); + continue; + } - // 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)) { + 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 << " in material '" << program->name.c_str() << "'"; - utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl; + utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'"; + utils::slog.w << " in material '" << program->name.c_str() << "'"; + utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl; #endif - texture = mEmptyTexture.get(); - } - - const SamplerParams& samplerParams = boundSampler->s; - VkSampler vksampler = mSamplerCache.getSampler(samplerParams); - - usage = VulkanPipelineCache::getUsageFlags(sampler.binding, samplerGroup.stageFlags, usage); - - 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); - } + texture = mEmptyTexture.get(); + } - samplerInfo[sampler.binding] = { - .sampler = vksampler, - .imageView = imageView, - .imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout()) - }; - samplerTextures[sampler.binding] = texture; - } + 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); } + + samplerInfo[binding] = { + .sampler = vksampler, + .imageView = imageView, + .imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout()) + }; + samplerTextures[binding] = texture; } mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage); diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h index 445bd0df9b8..16fa3a1b01c 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -112,6 +112,8 @@ class VulkanDriver final : public DriverBase { VulkanBlitter mBlitter; VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {}; VulkanReadPixels mReadPixels; + + bool const mIsSRGBSwapChainSupported; }; } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 963ca70f0f1..ac6b4ccea17 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -49,88 +49,100 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept : HwProgram(builder.getName()), VulkanResource(VulkanResourceType::PROGRAM), + mInfo(new PipelineInfo(builder.getSpecializationConstants().size())), mDevice(device) { - auto const& blobs = builder.getShadersSource(); - VkShaderModule* modules[2] = {&bundle.vertex, &bundle.fragment}; - // TODO: handle compute shaders. - for (size_t i = 0; i < 2; i++) { + auto& blobs = builder.getShadersSource(); + auto& modules = mInfo->shaders; + for (size_t i = 0; i < MAX_SHADER_MODULES; i++) { const auto& blob = blobs[i]; - VkShaderModule* module = modules[i]; - VkShaderModuleCreateInfo moduleInfo = {}; - moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - moduleInfo.codeSize = blob.size(); - moduleInfo.pCode = (uint32_t*) blob.data(); - VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, module); + uint32_t* data = (uint32_t*)blob.data(); + VkShaderModule& module = modules[i]; + VkShaderModuleCreateInfo moduleInfo = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = blob.size(), + .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(); - if (!specializationConstants.empty()) { - // Allocate a single heap block to store all the specialization constants structures - // our supported types are int32, float and bool, so we use 4 bytes per data. bool will - // just use the first byte. - char* pStorage = (char*)malloc( - sizeof(VkSpecializationInfo) + - specializationConstants.size() * sizeof(VkSpecializationMapEntry) + - specializationConstants.size() * 4); - - VkSpecializationInfo* const pInfo = (VkSpecializationInfo*)pStorage; - VkSpecializationMapEntry* const pEntries = - (VkSpecializationMapEntry*)(pStorage + sizeof(VkSpecializationInfo)); - void* pData = pStorage + sizeof(VkSpecializationInfo) + - specializationConstants.size() * sizeof(VkSpecializationMapEntry); - - *pInfo = { - .mapEntryCount = specializationConstants.size(), - .pMapEntries = pEntries, - .dataSize = specializationConstants.size() * 4, - .pData = pData, + 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); + } + } - for (size_t i = 0; i < specializationConstants.size(); i++) { - uint32_t const offset = uint32_t(i) * 4; - pEntries[i] = { - .constantID = specializationConstants[i].id, - .offset = offset, - // Note that bools are 4-bytes in Vulkan - // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html - .size = 4, - }; - - using SpecConstant = Program::SpecializationConstant::Type; - char const* addr = (char*)pData + 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); - } + auto& groupInfo = builder.getSamplerGroupInfo(); + auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; + auto& usage = mInfo->usage; + for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) { + auto const& group = groupInfo[groupInd]; + auto const& samplers = group.samplers; + for (size_t i = 0; i < samplers.size(); ++i) { + uint32_t const binding = samplers[i].binding; + bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i); + usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage); } - bundle.specializationInfos = pInfo; } - // Make a copy of the binding map - samplerGroupInfo = builder.getSamplerGroupInfo(); #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) noexcept +VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs, + CustomSamplerInfoList const& samplerInfo) noexcept : VulkanResource(VulkanResourceType::PROGRAM), + mInfo(new PipelineInfo(0)), mDevice(device) { - bundle.vertex = vs; - bundle.fragment = fs; + 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); + } } VulkanProgram::~VulkanProgram() { - vkDestroyShaderModule(mDevice, bundle.vertex, VKALLOC); - vkDestroyShaderModule(mDevice, bundle.fragment, VKALLOC); - free(bundle.specializationInfos); + for (auto shader: mInfo->shaders) { + vkDestroyShaderModule(mDevice, shader, VKALLOC); + } + delete mInfo; } // Creates a special "default" render target (i.e. associated with the swap chain) diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index 223a13dbd60..f60d73ca550 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -37,14 +37,64 @@ namespace filament::backend { class VulkanTimestamps; struct VulkanProgram : public HwProgram, VulkanResource { + VulkanProgram(VkDevice device, const Program& builder) noexcept; - VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept; + + struct CustomSamplerInfo { + uint8_t groupIndex; + uint8_t samplerIndex; + ShaderStageFlags flags; + }; + 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; ~VulkanProgram(); - VulkanPipelineCache::ProgramBundle bundle; - Program::SamplerGroupInfo samplerGroupInfo; + + inline VkShaderModule getVertexShader() const { + return mInfo->shaders[0]; + } + + 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; + } private: - VkDevice mDevice; + // 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; + + struct PipelineInfo { + PipelineInfo(size_t specConstsCount) : + bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff), + specConsts(specConstsCount, VkSpecializationMapEntry{}), + specConstData(new char[specConstsCount * 4]) + {} + + // 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; + + // 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; + }; + + PipelineInfo* mInfo; + VkDevice mDevice = VK_NULL_HANDLE; }; // The render target bundles together a set of attachments, each of which can have one of the diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index fb0ef037c4c..000f8c755c3 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -65,6 +65,13 @@ VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, Usa 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), @@ -558,12 +565,10 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP return &mPipelineLayouts.emplace(mPipelineRequirements.layout, cacheEntry).first.value(); } -void VulkanPipelineCache::bindProgram(const VulkanProgram& program) noexcept { - const VkShaderModule shaders[2] = { program.bundle.vertex, program.bundle.fragment }; - for (uint32_t ssi = 0; ssi < SHADER_MODULE_COUNT; ssi++) { - mPipelineRequirements.shaders[ssi] = shaders[ssi]; - } - mSpecializationRequirements = program.bundle.specializationInfos; +void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept { + mPipelineRequirements.shaders[0] = program->getVertexShader(); + mPipelineRequirements.shaders[1] = program->getFragmentShader(); + mSpecializationRequirements = &program->getSpecConstInfo(); } void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept { diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.h b/filament/backend/src/vulkan/VulkanPipelineCache.h index b7248420f8e..0e092e9555f 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.h +++ b/filament/backend/src/vulkan/VulkanPipelineCache.h @@ -91,6 +91,7 @@ class VulkanPipelineCache : public CommandBufferObserver { 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" @@ -150,7 +151,7 @@ class VulkanPipelineCache : public CommandBufferObserver { void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept; // Each of the following methods are fast and do not make Vulkan calls. - void bindProgram(const VulkanProgram& program) noexcept; + void bindProgram(VulkanProgram* program) noexcept; void bindRasterState(const RasterState& rasterState) noexcept; void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept; void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept; @@ -415,7 +416,7 @@ class VulkanPipelineCache : public CommandBufferObserver { RasterState mCurrentRasterState; PipelineKey mPipelineRequirements = {}; DescriptorKey mDescriptorRequirements = {}; - VkSpecializationInfo* mSpecializationRequirements = {}; + VkSpecializationInfo const* mSpecializationRequirements = nullptr; // Current bindings for the pipeline and descriptor sets. PipelineKey mBoundPipeline = {}; diff --git a/filament/backend/src/vulkan/VulkanSwapChain.cpp b/filament/backend/src/vulkan/VulkanSwapChain.cpp index d61c5bff691..e85844f951a 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.cpp +++ b/filament/backend/src/vulkan/VulkanSwapChain.cpp @@ -34,6 +34,8 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& mAllocator(allocator), mStagePool(stagePool), mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow), + mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize), + mImageReady(VK_NULL_HANDLE), mAcquired(false), mIsFirstRenderPass(true) { swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent); @@ -42,8 +44,13 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& VkSemaphoreCreateInfo const createInfo = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; - VkResult result = vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, &mImageReady); - ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore"); + + // 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"); + } update(); } @@ -55,7 +62,9 @@ VulkanSwapChain::~VulkanSwapChain() { mCommands->wait(); mPlatform->destroy(swapChain); - vkDestroySemaphore(mPlatform->getDevice(), mImageReady, VKALLOC); + if (mImageReady != VK_NULL_HANDLE) { + vkDestroySemaphore(mPlatform->getDevice(), mImageReady, VKALLOC); + } } void VulkanSwapChain::update() { @@ -109,8 +118,10 @@ void VulkanSwapChain::acquire(bool& resized) { // Check if the swapchain should be resized. if ((resized = mPlatform->hasResized(swapChain))) { - mCommands->flush(); - mCommands->wait(); + if (mFlushAndWaitOnResize) { + mCommands->flush(); + mCommands->wait(); + } mPlatform->recreate(swapChain); update(); } @@ -118,7 +129,9 @@ void VulkanSwapChain::acquire(bool& resized) { VkResult const result = mPlatform->acquire(swapChain, mImageReady, &mCurrentSwapIndex); ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR, "Cannot acquire in swapchain."); - mCommands->injectDependency(mImageReady); + if (mImageReady != VK_NULL_HANDLE) { + mCommands->injectDependency(mImageReady); + } mAcquired = true; } diff --git a/filament/backend/src/vulkan/VulkanSwapChain.h b/filament/backend/src/vulkan/VulkanSwapChain.h index 9c6be86a2a5..345dd2650d3 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.h +++ b/filament/backend/src/vulkan/VulkanSwapChain.h @@ -76,6 +76,7 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource { VmaAllocator mAllocator; VulkanStagePool& mStagePool; bool const mHeadless; + bool const mFlushAndWaitOnResize; // We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout // transitions, which are useful here. diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp index a0a1690b6d8..ec5c3fc93cf 100644 --- a/filament/backend/src/vulkan/VulkanTexture.cpp +++ b/filament/backend/src/vulkan/VulkanTexture.cpp @@ -61,9 +61,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, : HwTexture(target, levels, samples, w, h, depth, tformat, tusage), VulkanResource( heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE), - // Vulkan does not support 24-bit depth, use the official fallback format. - mVkFormat(tformat == TextureFormat::DEPTH24 ? context.getDepthFormat() - : backend::getVkFormat(tformat)), + mVkFormat(backend::getVkFormat(tformat)), mViewType(ImgUtil::getViewType(target)), mSwizzle(swizzle), mStagePool(stagePool), @@ -146,7 +144,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, // any kind of attachment (color or depth). const auto& limits = context.getPhysicalDeviceLimits(); if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) { - samples = reduceSampleCount(samples, isDepthFormat(mVkFormat) + samples = reduceSampleCount(samples, isVkDepthFormat(mVkFormat) ? limits.sampledImageDepthSampleCounts : limits.sampledImageColorSampleCounts); } @@ -454,7 +452,7 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres << "," << range.levelCount << ")" << " from=" << oldLayout << " to=" << newLayout << " format=" << mVkFormat - << " depth=" << isDepthFormat(mVkFormat) + << " depth=" << isVkDepthFormat(mVkFormat) << " slice-by-slice=" << transitionSliceBySlice << utils::io::endl; #endif diff --git a/filament/backend/src/vulkan/VulkanUtility.cpp b/filament/backend/src/vulkan/VulkanUtility.cpp index e718de4345d..612acb20e5d 100644 --- a/filament/backend/src/vulkan/VulkanUtility.cpp +++ b/filament/backend/src/vulkan/VulkanUtility.cpp @@ -121,8 +121,8 @@ VkFormat getVkFormat(TextureFormat format) { case TextureFormat::RGB8UI: return VK_FORMAT_R8G8B8A8_UINT; case TextureFormat::RGB8I: return VK_FORMAT_R8G8B8A8_SINT; - case TextureFormat::DEPTH24: - return VK_FORMAT_UNDEFINED; + // A 32-bit format but 8 bits are unused. + case TextureFormat::DEPTH24: return VK_FORMAT_X8_D24_UNORM_PACK32; // 32 bits per element. case TextureFormat::R32F: return VK_FORMAT_R32_SFLOAT; @@ -638,18 +638,12 @@ VkImageAspectFlags getImageAspect(VkFormat format) { } } -bool isDepthFormat(VkFormat format) { - switch (format) { - case VK_FORMAT_D16_UNORM: - case VK_FORMAT_X8_D24_UNORM_PACK32: - case VK_FORMAT_D16_UNORM_S8_UINT: - case VK_FORMAT_D24_UNORM_S8_UINT: - case VK_FORMAT_D32_SFLOAT: - case VK_FORMAT_D32_SFLOAT_S8_UINT: - return true; - default: - return false; - } +bool isVkDepthFormat(VkFormat format) { + return (getImageAspect(format) & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; +} + +bool isVkStencilFormat(VkFormat format) { + return (getImageAspect(format) & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; } static uint32_t mostSignificantBit(uint32_t x) { return 1ul << (31ul - utils::clz(x)); } diff --git a/filament/backend/src/vulkan/VulkanUtility.h b/filament/backend/src/vulkan/VulkanUtility.h index 9994cd69cfc..3ba35c75e69 100644 --- a/filament/backend/src/vulkan/VulkanUtility.h +++ b/filament/backend/src/vulkan/VulkanUtility.h @@ -41,7 +41,9 @@ VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags); bool equivalent(const VkRect2D& a, const VkRect2D& b); bool equivalent(const VkExtent2D& a, const VkExtent2D& b); -bool isDepthFormat(VkFormat format); +bool isVkDepthFormat(VkFormat format); +bool isVkStencilFormat(VkFormat format); + VkImageAspectFlags getImageAspect(VkFormat format); uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask); @@ -86,6 +88,10 @@ utils::FixedCapacityVector enumerate( #undef EXPAND_ENUM_NO_ARGS #undef EXPAND_ENUM_ARGS + +// Useful shorthands +using VkFormatList = utils::FixedCapacityVector; + } // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_VULKANUTILITY_H diff --git a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp index 153946a3f8b..3f3502ce684 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp @@ -420,9 +420,9 @@ inline int deviceTypeOrder(VkPhysicalDeviceType deviceType) { } VkPhysicalDevice selectPhysicalDevice(VkInstance instance, - VulkanPlatform::GPUPreference const& gpuPreference) { - FixedCapacityVector const physicalDevices - = filament::backend::enumerate(vkEnumeratePhysicalDevices, instance); + VulkanPlatform::Customization::GPUPreference const& gpuPreference) { + FixedCapacityVector const physicalDevices = + filament::backend::enumerate(vkEnumeratePhysicalDevices, instance); struct DeviceInfo { VkPhysicalDevice device = VK_NULL_HANDLE; VkPhysicalDeviceType deviceType = VK_PHYSICAL_DEVICE_TYPE_OTHER; @@ -488,10 +488,10 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance, return true; } if (!pref.deviceName.empty()) { - if (a.name.find(pref.deviceName) != a.name.npos) { + if (a.name.find(pref.deviceName.c_str()) != a.name.npos) { return false; } - if (b.name.find(pref.deviceName) != b.name.npos) { + if (b.name.find(pref.deviceName.c_str()) != b.name.npos) { return true; } } @@ -508,17 +508,28 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance, return device; } -VkFormat findSupportedFormat(VkPhysicalDevice device) { +VkFormatList findAttachmentDepthFormats(VkPhysicalDevice device) { VkFormatFeatureFlags const features = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; - VkFormat const formats[] = {VK_FORMAT_D32_SFLOAT, VK_FORMAT_X8_D24_UNORM_PACK32}; + + // The ordering here indicates the preference of choosing depth+stencil format. + VkFormat const formats[] = { + VK_FORMAT_D32_SFLOAT, + VK_FORMAT_X8_D24_UNORM_PACK32, + + VK_FORMAT_D32_SFLOAT_S8_UINT, + VK_FORMAT_D24_UNORM_S8_UINT, + }; + std::vector selectedFormats; for (VkFormat format: formats) { VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(device, format, &props); if ((props.optimalTilingFeatures & features) == features) { - return format; + selectedFormats.push_back(format); } } - return VK_FORMAT_UNDEFINED; + VkFormatList ret(selectedFormats.size()); + std::copy(selectedFormats.begin(), selectedFormats.end(), ret.begin()); + return ret; } }// anonymous namespace @@ -603,7 +614,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext, bluevk::bindInstance(mImpl->mInstance); - VulkanPlatform::GPUPreference const pref = getPreferredGPU(); + VulkanPlatform::Customization::GPUPreference const pref = getCustomization().gpu; bool const hasGPUPreference = pref.index >= 0 || !pref.deviceName.empty(); ASSERT_PRECONDITION(!(hasGPUPreference && sharedContext), "Cannot both share context and indicate GPU preference"); @@ -660,9 +671,9 @@ Driver* VulkanPlatform::createDriver(void* sharedContext, context.mDebugMarkersSupported = deviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != deviceExts.end(); - // Choose a depth format that meets our requirements. Take care not to include stencil formats - // just yet, since that would require a corollary change to the "aspect" flags for the VkImage. - context.mDepthFormat = findSupportedFormat(mImpl->mPhysicalDevice); + context.mDepthFormats = findAttachmentDepthFormats(mImpl->mPhysicalDevice); + + assert_invariant(context.mDepthFormats.size() > 0); #if FVK_ENABLED(FVK_DEBUG_VALIDATION) printDepthFormats(mImpl->mPhysicalDevice); diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp index af4fce092ec..e25d5f4af00 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatformAndroidLinuxWindows.cpp @@ -30,8 +30,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 { @@ -86,8 +84,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 +117,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/platform/VulkanPlatformSwapChainImpl.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp index f83e8be93b9..a5a31a8d95f 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp @@ -30,7 +30,7 @@ namespace { std::tuple createImageAndMemory(VulkanContext const& context, VkDevice device, VkExtent2D extent, VkFormat format) { - bool const isDepth = format == context.getDepthFormat(); + bool const isDepth = isVkDepthFormat(format); // Filament expects blit() to work with any texture, so we almost always set these usage flags. // TODO: investigate performance implications of setting these flags. VkImageUsageFlags const blittable @@ -76,6 +76,14 @@ std::tuple createImageAndMemory(VulkanContext const& co return std::tuple(image, imageMemory); } +VkFormat selectDepthFormat(VkFormatList const& depthFormats, bool hasStencil) { + auto const formatItr = std::find_if(depthFormats.begin(), depthFormats.end(), + hasStencil ? isVkStencilFormat : isVkDepthFormat); + assert_invariant( + formatItr != depthFormats.end() && "Cannot find suitable swapchain depth format"); + return *formatItr; +} + }// anonymous namespace VulkanPlatformSwapChainImpl::VulkanPlatformSwapChainImpl(VulkanContext const& context, @@ -116,7 +124,8 @@ VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext con mPhysicalDevice(physicalDevice), mSurface(surface), mFallbackExtent(fallbackExtent), - mUsesRGB((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0) { + mUsesRGB((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0), + mHasStencil((flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0) { assert_invariant(surface); create(); } @@ -152,10 +161,15 @@ VkResult VulkanPlatformSurfaceSwapChain::create() { // Find a suitable surface format. FixedCapacityVector const surfaceFormats = enumerate(vkGetPhysicalDeviceSurfaceFormatsKHR, mPhysicalDevice, mSurface); - FixedCapacityVector expectedFormats - = {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM}; + std::array expectedFormats = { + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_B8G8R8A8_UNORM, + }; if (mUsesRGB) { - expectedFormats = {VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB}; + expectedFormats = { + VK_FORMAT_R8G8B8A8_SRGB, + VK_FORMAT_B8G8R8A8_SRGB, + }; } for (VkSurfaceFormatKHR const& format: surfaceFormats) { if (std::any_of(expectedFormats.begin(), expectedFormats.end(), @@ -228,13 +242,18 @@ VkResult VulkanPlatformSurfaceSwapChain::create() { mSwapChainBundle.colors = enumerate(vkGetSwapchainImagesKHR, mDevice, mSwapchain); mSwapChainBundle.colorFormat = surfaceFormat.format; + mSwapChainBundle.depthFormat = + selectDepthFormat(mContext.getAttachmentDepthFormats(), mHasStencil); + mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat); + slog.i << "vkCreateSwapchain" << ": " << mSwapChainBundle.extent.width << "x" << mSwapChainBundle.extent.height << ", " << surfaceFormat.format << ", " << surfaceFormat.colorSpace << ", " - << mSwapChainBundle.colors.size() << ", " << caps.currentTransform << io::endl; + << "swapchain-size=" << mSwapChainBundle.colors.size() << ", " + << "identity-transform=" << (caps.currentTransform == 1) << ", " + << "depth=" << mSwapChainBundle.depthFormat + << io::endl; - mSwapChainBundle.depthFormat = mContext.getDepthFormat(); - mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat); return result; } @@ -309,7 +328,9 @@ VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext c images[i] = createImage(extent, mSwapChainBundle.colorFormat); } - mSwapChainBundle.depthFormat = context.getDepthFormat(); + bool const hasStencil = (flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0; + mSwapChainBundle.depthFormat = + selectDepthFormat(mContext.getAttachmentDepthFormats(), hasStencil); mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat); } @@ -325,20 +346,7 @@ VkResult VulkanPlatformHeadlessSwapChain::present(uint32_t index, VkSemaphore fi VkResult VulkanPlatformHeadlessSwapChain::acquire(VkSemaphore clientSignal, uint32_t* index) { *index = mCurrentIndex; mCurrentIndex = (mCurrentIndex + 1) % HEADLESS_SWAPCHAIN_SIZE; - VkSemaphore const localSignal = clientSignal; - VkSubmitInfo const submitInfo{ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .waitSemaphoreCount = 0, - .pWaitSemaphores = nullptr, - .pWaitDstStageMask = nullptr, - .commandBufferCount = 0, - .pCommandBuffers = nullptr, - .signalSemaphoreCount = 1u, - .pSignalSemaphores = &localSignal, - }; - UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, VK_NULL_HANDLE); - assert_invariant(result == VK_SUCCESS); - return result; + return VK_SUCCESS; } void VulkanPlatformHeadlessSwapChain::destroy() { diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h index ae2d022bb4f..ac4e26c105f 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h +++ b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h @@ -108,6 +108,7 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl { VkExtent2D const mFallbackExtent; bool mUsesRGB = false; + bool mHasStencil = false; bool mSuboptimal; }; 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/BufferObject.h b/filament/include/filament/BufferObject.h index 1ede31b8cb0..edabba83fa4 100644 --- a/filament/include/filament/BufferObject.h +++ b/filament/include/filament/BufferObject.h @@ -110,6 +110,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 ea26343898a..ab06a413b58 100644 --- a/filament/include/filament/Camera.h +++ b/filament/include/filament/Camera.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace utils { class Entity; @@ -172,6 +173,30 @@ class UTILS_PUBLIC Camera : public FilamentAPI { HORIZONTAL //!< the field-of-view angle is defined on the horizontal axis }; + /** Returns the projection matrix from the field-of-view. + * + * @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180. + * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param direction direction of the \p fovInDegrees parameter. + * + * @see Fov. + */ + static math::mat4 projection(Fov direction, double fovInDegrees, + double aspect, double near, double far = std::numeric_limits::infinity()); + + /** Returns the projection matrix from the focal length. + * + * @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0. + * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @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()); + + /** Sets the projection matrix from a frustum defined by six planes. * * @param projection type of #Projection to use. @@ -209,7 +234,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { double bottom, double top, double near, double far); - /** Sets the projection matrix from the field-of-view. + + /** Utility to set the projection matrix from the field-of-view. * * @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180. * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. @@ -222,7 +248,7 @@ class UTILS_PUBLIC Camera : public FilamentAPI { void setProjection(double fovInDegrees, double aspect, double near, double far, Fov direction = Fov::VERTICAL); - /** Sets the projection matrix from the focal length. + /** Utility to set the projection matrix from the focal length. * * @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0. * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. @@ -232,13 +258,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { void setLensProjection(double focalLengthInMillimeters, double aspect, double near, double far); + /** Sets a custom projection matrix. - * - * The projection matrix must be of one of the following form: - * a 0 tx 0 a 0 0 tx - * 0 b ty 0 0 b 0 ty - * 0 0 tz c 0 0 c tz - * 0 0 -1 0 0 0 0 1 * * The projection matrix must define an NDC system that must match the OpenGL convention, * that is all 3 axis are mapped to [-1, 1]. @@ -249,6 +270,19 @@ class UTILS_PUBLIC Camera : public FilamentAPI { */ void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept; + /** Sets the projection matrix. + * + * The projection matrices must define an NDC system that must match the OpenGL convention, + * that is all 3 axis are mapped to [-1, 1]. + * + * @param projection custom projection matrix used for rendering + * @param projectionForCulling custom projection matrix used for culling + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @param far distance in world units from the camera to the far plane. \p far > \p near. + */ + void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, + double near, double far) noexcept; + /** Sets a custom projection matrix for each eye. * * The projectionForCulling, near, and far parameters establish a "culling frustum" which must @@ -267,25 +301,6 @@ class UTILS_PUBLIC Camera : public FilamentAPI { void setCustomEyeProjection(math::mat4 const* projection, size_t count, math::mat4 const& projectionForCulling, double near, double far); - /** Sets the projection matrix. - * - * The projection matrices must be of one of the following form: - * a 0 tx 0 a 0 0 tx - * 0 b ty 0 0 b 0 ty - * 0 0 tz c 0 0 c tz - * 0 0 -1 0 0 0 0 1 - * - * The projection matrices must define an NDC system that must match the OpenGL convention, - * that is all 3 axis are mapped to [-1, 1]. - * - * @param projection custom projection matrix used for rendering - * @param projectionForCulling custom projection matrix used for culling - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. - */ - void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, - double near, double far) noexcept; - /** Sets an additional matrix that scales the projection matrix. * * This is useful to adjust the aspect ratio of the camera independent from its projection. @@ -524,33 +539,6 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * * \param p the projection matrix to inverse * \returns the inverse of the projection matrix \p p - * - * \warning the projection matrix to invert must have one of the form below: - * - perspective projection - * - * \f$ - * \left( - * \begin{array}{cccc} - * a & 0 & tx & 0 \\ - * 0 & b & ty & 0 \\ - * 0 & 0 & tz & c \\ - * 0 & 0 & -1 & 0 \\ - * \end{array} - * \right) - * \f$ - * - * - orthographic projection - * - * \f$ - * \left( - * \begin{array}{cccc} - * a & 0 & 0 & tx \\ - * 0 & b & 0 & ty \\ - * 0 & 0 & c & tz \\ - * 0 & 0 & 0 & 1 \\ - * \end{array} - * \right) - * \f$ */ static math::mat4 inverseProjection(const math::mat4& p) noexcept; @@ -577,6 +565,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/ColorGrading.h b/filament/include/filament/ColorGrading.h index db709600ef3..0cd4e8618a7 100644 --- a/filament/include/filament/ColorGrading.h +++ b/filament/include/filament/ColorGrading.h @@ -478,6 +478,10 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { private: friend class FColorGrading; }; + +protected: + // prevent heap allocation + ~ColorGrading() = default; }; } // namespace filament diff --git a/filament/include/filament/DebugRegistry.h b/filament/include/filament/DebugRegistry.h index b5fb8f21365..f4df9e07a5d 100644 --- a/filament/include/filament/DebugRegistry.h +++ b/filament/include/filament/DebugRegistry.h @@ -67,15 +67,28 @@ class UTILS_PUBLIC DebugRegistry : public FilamentAPI { * @return Address of the data of the \p name property * @{ */ - void* getPropertyAddress(const char* name) noexcept; + void* getPropertyAddress(const char* name); + + void const* getPropertyAddress(const char* name) const noexcept; + + template + inline T* getPropertyAddress(const char* name) { + return static_cast(getPropertyAddress(name)); + } template - inline T* getPropertyAddress(const char* name) noexcept { + inline T const* getPropertyAddress(const char* name) const noexcept { return static_cast(getPropertyAddress(name)); } template - inline bool getPropertyAddress(const char* name, T** p) noexcept { + inline bool getPropertyAddress(const char* name, T** p) { + *p = getPropertyAddress(name); + return *p != nullptr; + } + + template + inline bool getPropertyAddress(const char* name, T* const* p) const noexcept { *p = getPropertyAddress(name); return *p != nullptr; } @@ -129,6 +142,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 30d01526fd6..80ef41856b8 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -268,6 +268,19 @@ class UTILS_PUBLIC Engine { */ uint32_t perFrameCommandsSizeMB = FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB; + /** + * Number of threads to use in Engine's JobSystem. + * + * Engine uses a utils::JobSystem to carry out paralleization of Engine workloads. This + * value sets the number of threads allocated for JobSystem. Configuring this value can be + * helpful in CPU-constrained environments where too many threads can cause contention of + * CPU and reduce performance. + * + * The default value is 0, which implies that the Engine will use a heuristic to determine + * the number of threads to use. + */ + uint32_t jobSystemThreadCount = 0; + /* * Number of most-recently destroyed textures to track for use-after-free. * diff --git a/filament/include/filament/Fence.h b/filament/include/filament/Fence.h index bcfd2871fd3..56cb33e66f2 100644 --- a/filament/include/filament/Fence.h +++ b/filament/include/filament/Fence.h @@ -75,6 +75,10 @@ class UTILS_PUBLIC Fence : public FilamentAPI { * FenceStatus::ERROR otherwise. */ static FenceStatus waitAndDestroy(Fence* 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/IndexBuffer.h b/filament/include/filament/IndexBuffer.h index 09b479296d5..09e433a36dd 100644 --- a/filament/include/filament/IndexBuffer.h +++ b/filament/include/filament/IndexBuffer.h @@ -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..4c3c6e2c38e 100644 --- a/filament/include/filament/IndirectLight.h +++ b/filament/include/filament/IndirectLight.h @@ -342,6 +342,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..671114a93f1 100644 --- a/filament/include/filament/InstanceBuffer.h +++ b/filament/include/filament/InstanceBuffer.h @@ -91,6 +91,10 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { * @param offset index of the first instance to set local transforms */ void setLocalTransforms(math::mat4f const* 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..fa0bdee71c8 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -143,20 +143,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 +157,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* getEntities() const noexcept; + /** * Gets an Instance representing the Light component associated with the given Entity. * @param e An Entity. @@ -953,19 +964,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..e6f374be910 100644 --- a/filament/include/filament/Material.h +++ b/filament/include/filament/Material.h @@ -375,6 +375,10 @@ class UTILS_PUBLIC Material : public FilamentAPI { //! Returns this material's default instance. MaterialInstance const* 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..0c6ef205730 100644 --- a/filament/include/filament/MaterialInstance.h +++ b/filament/include/filament/MaterialInstance.h @@ -479,6 +479,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..e94854643e1 100644 --- a/filament/include/filament/MorphTargetBuffer.h +++ b/filament/include/filament/MorphTargetBuffer.h @@ -136,6 +136,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 a141d64b9fb..f09fd4dac36 100644 --- a/filament/include/filament/Options.h +++ b/filament/include/filament/Options.h @@ -479,7 +479,8 @@ enum class ShadowType : uint8_t { PCF, //!< percentage-closer filtered shadows (default) VSM, //!< variance shadows DPCF, //!< PCF with contact hardening simulation - PCSS //!< PCF with soft shadows and contact hardening + PCSS, //!< PCF with soft shadows and contact hardening + PCFd, // for debugging only, don't use. }; /** diff --git a/filament/include/filament/RenderTarget.h b/filament/include/filament/RenderTarget.h index 508e1c246f2..faeee80cf6e 100644 --- a/filament/include/filament/RenderTarget.h +++ b/filament/include/filament/RenderTarget.h @@ -180,6 +180,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..8383a94748f 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -102,6 +102,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* getEntities() const noexcept; + /** * The transformation associated with a skinning joint. * @@ -829,6 +852,10 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { typename = typename is_supported_index_type::type> static Box computeAABB(VECTOR const* vertices, INDEX const* 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, diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h index 111026bbcf2..8295a06fed3 100644 --- a/filament/include/filament/Renderer.h +++ b/filament/include/filament/Renderer.h @@ -173,6 +173,12 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { */ void setClearOptions(const ClearOptions& options); + /** + * Returns the ClearOptions currently set. + * @return A reference to a ClearOptions structure. + */ + ClearOptions const& getClearOptions() const noexcept; + /** * Get the Engine that created this Renderer. * @@ -573,6 +579,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..28361278a2f 100644 --- a/filament/include/filament/Scene.h +++ b/filament/include/filament/Scene.h @@ -140,16 +140,22 @@ class UTILS_PUBLIC Scene : public FilamentAPI { void removeEntities(const utils::Entity* 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 +174,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..5c334563602 100644 --- a/filament/include/filament/SkinningBuffer.h +++ b/filament/include/filament/SkinningBuffer.h @@ -115,6 +115,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..150c9d3e4cf 100644 --- a/filament/include/filament/Skybox.h +++ b/filament/include/filament/Skybox.h @@ -174,6 +174,10 @@ class UTILS_PUBLIC Skybox : public FilamentAPI { * @return the associated texture, or null if it does not exist */ Texture const* 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..83f898acae2 100644 --- a/filament/include/filament/Stream.h +++ b/filament/include/filament/Stream.h @@ -207,6 +207,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..c17523acd5d 100644 --- a/filament/include/filament/SwapChain.h +++ b/filament/include/filament/SwapChain.h @@ -286,6 +286,10 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { void setFrameCompletedCallback(backend::CallbackHandler* 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..bb011cc9781 100644 --- a/filament/include/filament/Texture.h +++ b/filament/include/filament/Texture.h @@ -541,6 +541,10 @@ class UTILS_PUBLIC Texture : public FilamentAPI { return *this; } }; + +protected: + // prevent heap allocation + ~Texture() = default; }; } // namespace filament diff --git a/filament/include/filament/ToneMapper.h b/filament/include/filament/ToneMapper.h index 8d19c59756b..d220f47b662 100644 --- a/filament/include/filament/ToneMapper.h +++ b/filament/include/filament/ToneMapper.h @@ -115,6 +115,30 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { 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 + GOLDEN //!< A golden tinted, slightly washed look for BT.1886 displays + }; + + /** + * Builds a new AgX tone mapper. + * + * @param look an optional creative adjustment to contrast and saturation + */ + explicit AgxToneMapper(AgxLook look = AgxLook::NONE) noexcept; + ~AgxToneMapper() noexcept final; + + math::float3 operator()(math::float3 x) const noexcept override; + + AgxLook look; +}; + /** * Generic tone mapping operator that gives control over the tone mapping * curve. This operator can be used to control the aesthetics of the final diff --git a/filament/include/filament/TransformManager.h b/filament/include/filament/TransformManager.h index 9afa6897128..73ac2c99005 100644 --- a/filament/include/filament/TransformManager.h +++ b/filament/include/filament/TransformManager.h @@ -118,6 +118,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* getEntities() const noexcept; + /** * Enables or disable the accurate translation mode. Disabled by default. * @@ -261,7 +284,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 +302,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 +331,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..317d5a7368a 100644 --- a/filament/include/filament/VertexBuffer.h +++ b/filament/include/filament/VertexBuffer.h @@ -207,6 +207,10 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { * @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); + +protected: + // prevent heap allocation + ~VertexBuffer() = default; }; } // namespace filament diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 4bba745d1f3..5eddb99143b 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -894,6 +894,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/src/Camera.cpp b/filament/src/Camera.cpp index f6fa5d95c0f..b88080874ea 100644 --- a/filament/src/Camera.cpp +++ b/filament/src/Camera.cpp @@ -22,49 +22,28 @@ namespace filament { using namespace math; -template -details::TMat44 inverseProjection(const details::TMat44& p) noexcept { - details::TMat44 r; - const T A = 1 / p[0][0]; - const T B = 1 / p[1][1]; - if (p[2][3] != T(0)) { - // perspective projection - // a 0 tx 0 - // 0 b ty 0 - // 0 0 tz c - // 0 0 -1 0 - const T C = 1 / p[3][2]; - r[0][0] = A; - r[1][1] = B; - r[2][2] = 0; - r[2][3] = C; - r[3][0] = p[2][0] * A; // not needed if symmetric - r[3][1] = p[2][1] * B; // not needed if symmetric - r[3][2] = -1; - r[3][3] = p[2][2] * C; - } else { - // orthographic projection - // a 0 0 tx - // 0 b 0 ty - // 0 0 c tz - // 0 0 0 1 - const T C = 1 / p[2][2]; - r[0][0] = A; - r[1][1] = B; - r[2][2] = C; - r[3][3] = 1; - r[3][0] = -p[3][0] * A; - r[3][1] = -p[3][1] * B; - r[3][2] = -p[3][2] * C; - } - return r; +void Camera::setProjection(double fovInDegrees, double aspect, double near, double far, + Camera::Fov direction) { + setCustomProjection( + projection(direction, fovInDegrees, aspect, near), + projection(direction, fovInDegrees, aspect, near, far), + near, far); +} + +void Camera::setLensProjection(double focalLengthInMillimeters, + double aspect, double near, double far) { + setCustomProjection( + projection(focalLengthInMillimeters, aspect, near), + projection(focalLengthInMillimeters, aspect, near, far), + near, far); } mat4f Camera::inverseProjection(const mat4f& p) noexcept { - return filament::inverseProjection(p); + return inverse(p); } -mat4 Camera::inverseProjection(const mat4 & p) noexcept { - return filament::inverseProjection(p); + +mat4 Camera::inverseProjection(const mat4& p) noexcept { + return inverse(p); } void Camera::setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model) { @@ -81,16 +60,6 @@ void Camera::setProjection(Camera::Projection projection, double left, double ri downcast(this)->setProjection(projection, left, right, bottom, top, near, far); } -void Camera::setProjection(double fovInDegrees, double aspect, double near, double far, - Camera::Fov direction) { - downcast(this)->setProjection(fovInDegrees, aspect, near, far, direction); -} - -void Camera::setLensProjection(double focalLengthInMillimeters, - double aspect, double near, double far) { - downcast(this)->setLensProjection(focalLengthInMillimeters, aspect, near, far); -} - void Camera::setCustomProjection(mat4 const& projection, double near, double far) noexcept { downcast(this)->setCustomProjection(projection, near, far); } @@ -216,4 +185,14 @@ double Camera::computeEffectiveFov(double fovInDegrees, double focusDistance) no return FCamera::computeEffectiveFov(fovInDegrees, focusDistance); } +math::mat4 Camera::projection(Fov direction, double fovInDegrees, + double aspect, double near, double far) { + return FCamera::projection(direction, fovInDegrees, aspect, near, far); +} + +math::mat4 Camera::projection(double focalLengthInMillimeters, + double aspect, double near, double far) { + return FCamera::projection(focalLengthInMillimeters, aspect, near, far); +} + } // namespace filament diff --git a/filament/src/Culler.h b/filament/src/Culler.h index ad5fd4f3c87..6f3a0bf9f90 100644 --- a/filament/src/Culler.h +++ b/filament/src/Culler.h @@ -37,12 +37,12 @@ namespace filament { class Culler { public: // Culler can only process buffers with a size multiple of MODULO - static constexpr size_t MODULO = 4u; + static constexpr size_t MODULO = 8u; static inline size_t round(size_t count) noexcept { return (count + (MODULO - 1)) & ~(MODULO - 1); } - using result_type = uint16_t; + using result_type = uint8_t; /* * returns whether each AABB in an array intersects with the frustum diff --git a/filament/src/DebugRegistry.cpp b/filament/src/DebugRegistry.cpp index 5a90de2adcb..a4f93df4864 100644 --- a/filament/src/DebugRegistry.cpp +++ b/filament/src/DebugRegistry.cpp @@ -77,7 +77,11 @@ bool DebugRegistry::getProperty(const char* name, float4* v) const noexcept { return downcast(this)->getProperty(name, v); } -void *DebugRegistry::getPropertyAddress(const char *name) noexcept { +void *DebugRegistry::getPropertyAddress(const char *name) { + return downcast(this)->getPropertyAddress(name); +} + +void const *DebugRegistry::getPropertyAddress(const char *name) const noexcept { return downcast(this)->getPropertyAddress(name); } 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/PerShadowMapUniforms.cpp b/filament/src/PerShadowMapUniforms.cpp index 4dd9dface4d..87b78a9a9e4 100644 --- a/filament/src/PerShadowMapUniforms.cpp +++ b/filament/src/PerShadowMapUniforms.cpp @@ -61,7 +61,7 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction, s.clipFromWorldMatrix[0] = clipFromWorld; // projection * view s.worldFromClipMatrix = worldFromClip; // 1/(projection * view) s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin)); - s.clipTransform = camera.clipTransfrom; + s.clipTransform = camera.clipTransform; s.cameraFar = camera.zf; s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn); s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn); diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index a7b6513fbcb..6ea6f4f629d 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -76,7 +76,7 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n s.viewFromClipMatrix = viewFromClip; // 1/projection s.worldFromClipMatrix = worldFromClip; // 1/(projection * view) s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin)); - s.clipTransform = camera.clipTransfrom; + s.clipTransform = camera.clipTransform; s.cameraFar = camera.zf; s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn); s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn); @@ -429,6 +429,17 @@ void PerViewUniforms::prepareShadowPCSS(Handle texture, PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms); } +void PerViewUniforms::prepareShadowPCFDebug(Handle texture, + ShadowMappingUniforms const& shadowMappingUniforms) noexcept { + mSamplers.setSampler(PerViewSib::SHADOW_MAP, { texture, { + .filterMag = SamplerMagFilter::NEAREST, + .filterMin = SamplerMinFilter::NEAREST + }}); + auto& s = mUniforms.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCF; + PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms); +} + void PerViewUniforms::commit(backend::DriverApi& driver) noexcept { if (mUniforms.isDirty()) { driver.updateBufferObject(mUniformBufferHandle, mUniforms.toBufferDescriptor(driver), 0); diff --git a/filament/src/PerViewUniforms.h b/filament/src/PerViewUniforms.h index f5be309bf13..8fb5f529cce 100644 --- a/filament/src/PerViewUniforms.h +++ b/filament/src/PerViewUniforms.h @@ -128,6 +128,9 @@ class PerViewUniforms { ShadowMappingUniforms const& shadowMappingUniforms, SoftShadowOptions const& options) noexcept; + void prepareShadowPCFDebug(TextureHandle texture, + ShadowMappingUniforms const& shadowMappingUniforms) noexcept; + // update local data into GPU UBO void commit(backend::DriverApi& driver) noexcept; diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 4f82b2ddcd0..4f176c10142 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -2503,7 +2503,7 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv inoutCameraInfo->projection[2].xy -= jitterInClipSpace; // VERTEX_DOMAIN_DEVICE doesn't apply the projection, but it still needs this // clip transform, so we apply it separately (see main.vs) - inoutCameraInfo->clipTransfrom.zw -= jitterInClipSpace; + inoutCameraInfo->clipTransform.zw -= jitterInClipSpace; fg.addTrivialSideEffectPass("Jitter Camera", [=, &uniforms] (DriverApi& driver) { diff --git a/filament/src/RenderableManager.cpp b/filament/src/RenderableManager.cpp index bc1240415d1..71a83bf544c 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); diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 8df2351dcde..85d58f9ec15 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -81,6 +81,10 @@ void Renderer::setClearOptions(const ClearOptions& options) { downcast(this)->setClearOptions(options); } +Renderer::ClearOptions const& Renderer::getClearOptions() const noexcept { + return downcast(this)->getClearOptions(); +} + void Renderer::renderStandaloneView(View const* view) { downcast(this)->renderStandaloneView(downcast(view)); } 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.h b/filament/src/ShadowMap.h index af78e271a40..3849710c3b3 100644 --- a/filament/src/ShadowMap.h +++ b/filament/src/ShadowMap.h @@ -39,12 +39,12 @@ class RenderPass; // The value of the 'VISIBLE_MASK' after culling. Each bit represents visibility in a frustum // (either camera or light). // -// 1 -// bits 5 ... 7 6 5 4 3 2 1 0 -// +------------------------------------------------------+ -// VISIBLE_RENDERABLE X -// VISIBLE_DIR_SHADOW_RENDERABLE X -// VISIBLE_DYN_SHADOW_RENDERABLE X +// +// bits 7 6 5 4 3 2 1 0 +// +---------------------------------------------+ +// VISIBLE_RENDERABLE X +// VISIBLE_DIR_SHADOW_RENDERABLE X +// VISIBLE_DYN_SHADOW_RENDERABLE X // A "shadow renderable" is a renderable rendered to the shadow map during a shadow pass: // PCF shadows: only shadow casters @@ -54,6 +54,7 @@ static constexpr size_t VISIBLE_RENDERABLE_BIT = 0u; static constexpr size_t VISIBLE_DIR_SHADOW_RENDERABLE_BIT = 1u; static constexpr size_t VISIBLE_DYN_SHADOW_RENDERABLE_BIT = 2u; +static constexpr Culler::result_type VISIBLE_RENDERABLE = 1u << VISIBLE_RENDERABLE_BIT; static constexpr Culler::result_type VISIBLE_DIR_SHADOW_RENDERABLE = 1u << VISIBLE_DIR_SHADOW_RENDERABLE_BIT; static constexpr Culler::result_type VISIBLE_DYN_SHADOW_RENDERABLE = 1u << VISIBLE_DYN_SHADOW_RENDERABLE_BIT; diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 34c47519e6b..30880c96a9e 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -446,21 +446,65 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight); // Adjust the camera's projection for the light's shadowFar - cameraInfo.zf = params.options.shadowFar > 0.0f ? params.options.shadowFar : cameraInfo.zf; if (UTILS_UNLIKELY(params.options.shadowFar > 0.0f)) { cameraInfo.zf = params.options.shadowFar; float const n = cameraInfo.zn; float const f = cameraInfo.zf; - if (std::abs(cameraInfo.cullingProjection[2].w) > std::numeric_limits::epsilon()) { - // perspective projection - cameraInfo.cullingProjection[2].z = (f + n) / (n - f); - cameraInfo.cullingProjection[3].z = (2 * f * n) / (n - f); - } else { - // orthographic projection - cameraInfo.cullingProjection[2].z = 2.0f / (n - f); - cameraInfo.cullingProjection[3].z = (f + n) / (n - f); - } + + /* + * Updating a projection matrix near and far planes: + * + * We assume that the near and far plane equations are of the form: + * N = { 0, 0, 1, n } + * F = { 0, 0, -1, -f } + * + * We also assume that the lower-left 2x2 of the projection is all 0: + * P = A 0 C 0 + * 0 B D 0 + * 0 0 E F + * 0 0 G H + * + * It result that we need to calculate E and F while leaving all other parameter unchanged. + * + * We know that: + * with N, F the near/far normalized plane equation parameters + * sn, sf arbitrary scale factors (they don't affect the planes) + * m is the transpose of projection (see Frustum.cpp) + * + * sn * N == -m[3] - m[2] + * sf * F == -m[3] + m[2] + * + * sn * N + sf * F == -2 * m[3] + * sn * N - sf * F == -2 * m[2] + * + * sn * N.z + sf * F.z == -2 * m[3].z + * sn * N.w + sf * F.w == -2 * m[3].w + * sn * N.z - sf * F.z == -2 * m[2].z + * sn * N.w - sf * F.w == -2 * m[2].w + * + * sn * N.z + sf * F.z == -2 * p[2].w + * sn * N.w + sf * F.w == -2 * p[3].w + * sn * N.z - sf * F.z == -2 * p[2].z + * sn * N.w - sf * F.w == -2 * p[3].z + * + * We now need to solve for { p[2].z, p[3].z, sn, sf } : + * + * sn == -2 * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z) + * sf == -2 * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z) + * p[2].z == (sf * F.z - sn * N.z) / 2 + * p[3].z == (sf * F.w - sn * N.w) / 2 + */ + auto& p = cameraInfo.cullingProjection; + float4 const N = { 0, 0, 1, n }; // near plane equation + float4 const F = { 0, 0, -1, -f }; // far plane equation + // near plane equation scale factor + float const sn = -2.0f * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z); + // far plane equation scale factor + float const sf = -2.0f * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z); + // New values for the projection + p[2].z = (sf * F.z - sn * N.z) * 0.5f; + p[3].z = (sf * F.w - sn * N.w) * 0.5f; } const ShadowMap::ShadowMapInfo shadowMapInfo{ diff --git a/filament/src/ToneMapper.cpp b/filament/src/ToneMapper.cpp index d3dcdd698af..b24f253e0a1 100644 --- a/filament/src/ToneMapper.cpp +++ b/filament/src/ToneMapper.cpp @@ -230,6 +230,106 @@ float3 FilmicToneMapper::operator()(math::float3 x) const noexcept { return (x * (a * x + b)) / (x * (c * x + d) + e); } +//------------------------------------------------------------------------------ +// AgX tone mapper +//------------------------------------------------------------------------------ + +AgxToneMapper::AgxToneMapper(AgxToneMapper::AgxLook look) noexcept : look(look) {} +AgxToneMapper::~AgxToneMapper() noexcept = default; + +// These matrices taken from Blender's implementation of AgX, which works with Rec.2020 primaries. +// https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBaseRec2020.py +constexpr mat3f AgXInsetMatrix { + 0.856627153315983, 0.137318972929847, 0.11189821299995, + 0.0951212405381588, 0.761241990602591, 0.0767994186031903, + 0.0482516061458583, 0.101439036467562, 0.811302368396859 +}; +constexpr mat3f AgXOutsetMatrixInv { + 0.899796955911611, 0.11142098895748, 0.11142098895748, + 0.0871996192028351, 0.875575586156966, 0.0871996192028349, + 0.013003424885555, 0.0130034248855548, 0.801379391839686 +}; +constexpr mat3f AgXOutsetMatrix { inverse(AgXOutsetMatrixInv) }; + +// LOG2_MIN = -10.0 +// LOG2_MAX = +6.5 +// MIDDLE_GRAY = 0.18 +const float AgxMinEv = -12.47393f; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY) +const float AgxMaxEv = 4.026069f; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY) + +// Adapted from https://iolite-engine.com/blog_posts/minimal_agx_implementation +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; +} + +// Adapted from https://iolite-engine.com/blog_posts/minimal_agx_implementation +float3 agxLook(float3 val, AgxToneMapper::AgxLook look) { + if (look == AgxToneMapper::AgxLook::NONE) { + return val; + } + + const float3 lw = float3(0.2126, 0.7152, 0.0722); + 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; + + if (look == AgxToneMapper::AgxLook::GOLDEN) { + slope = float3(1.0, 0.9, 0.5); + power = float3(0.8); + sat = 1.3; + } + if (look == AgxToneMapper::AgxLook::PUNCHY) { + slope = float3(1.0); + power = float3(1.35, 1.35, 1.35); + sat = 1.4; + } + + // ASC CDL + val = pow(val * slope + offset, power); + return luma + sat * (val - luma); +} + +float3 AgxToneMapper::operator()(float3 v) const noexcept { + // Ensure no negative values + v = max(float3(0.0), v); + + v = AgXInsetMatrix * v; + + // Log2 encoding + v = max(v, 1E-10); // avoid 0 or negative numbers for log2 + v = log2(v); + v = (v - AgxMinEv) / (AgxMaxEv - AgxMinEv); + + v = clamp(v, 0, 1); + + // Apply sigmoid + v = agxDefaultContrastApprox(v); + + // Apply AgX look + v = agxLook(v, look); + + v = AgXOutsetMatrix * v; + + // Linearize + v = pow(max(float3(0.0), v), 2.2); + + return v; +} + //------------------------------------------------------------------------------ // Display range tone mapper //------------------------------------------------------------------------------ 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/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..11e4eba8f5b 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -678,7 +678,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. diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index d79d2ca3eeb..746c46dbeee 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -92,7 +92,23 @@ 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); @@ -176,10 +192,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, 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 8ed72152c61..3ea6c5b3bc4 100644 --- a/filament/src/details/Camera.cpp +++ b/filament/src/details/Camera.cpp @@ -47,11 +47,11 @@ FCamera::FCamera(FEngine& engine, Entity e) mEntity(e) { } -void UTILS_NOINLINE FCamera::setProjection(double fovInDegrees, double aspect, double near, double far, - Camera::Fov direction) { +math::mat4 FCamera::projection(Fov direction, double fovInDegrees, + double aspect, double near, double far) { double w; double h; - double s = std::tan(fovInDegrees * math::d::DEG_TO_RAD / 2.0) * near; + double const s = std::tan(fovInDegrees * math::d::DEG_TO_RAD / 2.0) * near; if (direction == Fov::VERTICAL) { w = s * aspect; h = s; @@ -59,29 +59,35 @@ void UTILS_NOINLINE FCamera::setProjection(double fovInDegrees, double aspect, d w = s; h = s / aspect; } - FCamera::setProjection(Projection::PERSPECTIVE, -w, w, -h, h, near, far); + mat4 p = math::mat4::frustum(-w, w, -h, h, near, far); + if (far == std::numeric_limits::infinity()) { + p[2][2] = -1.0f; // lim(far->inf) = -1 + p[3][2] = -2.0f * near; // lim(far->inf) = -2*near + } + return p; } -void FCamera::setLensProjection(double focalLengthInMillimeters, +math::mat4 FCamera::projection(double focalLengthInMillimeters, double aspect, double near, double far) { // a 35mm camera has a 36x24mm wide frame size - double h = (0.5 * near) * ((SENSOR_SIZE * 1000.0) / focalLengthInMillimeters); - double w = h * aspect; - FCamera::setProjection(Projection::PERSPECTIVE, -w, w, -h, h, near, far); + double const h = (0.5 * near) * ((SENSOR_SIZE * 1000.0) / focalLengthInMillimeters); + double const w = h * aspect; + mat4 p = math::mat4::frustum(-w, w, -h, h, near, far); + if (far == std::numeric_limits::infinity()) { + p[2][2] = -1.0f; // lim(far->inf) = -1 + p[3][2] = -2.0f * near; // lim(far->inf) = -2*near + } + return p; } /* * All methods for setting the projection funnel through here */ -void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p, double near, double far) noexcept { - setCustomProjection(p, p, near, far); -} - void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p, mat4 const& c, double near, double far) noexcept { - for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) { - mEyeProjection[i] = p; + for (auto& eyeProjection: mEyeProjection) { + eyeProjection = p; } mProjectionForCulling = c; mNear = near; @@ -237,50 +243,12 @@ double FCamera::computeEffectiveFocalLength(double focalLength, double focusDist } double FCamera::computeEffectiveFov(double fovInDegrees, double focusDistance) noexcept { - double f = 0.5 * FCamera::SENSOR_SIZE / std::tan(fovInDegrees * math::d::DEG_TO_RAD * 0.5); + double const f = 0.5 * FCamera::SENSOR_SIZE / std::tan(fovInDegrees * math::d::DEG_TO_RAD * 0.5); focusDistance = std::max(f, focusDistance); - double fov = 2.0 * std::atan(FCamera::SENSOR_SIZE * (focusDistance - f) / (2.0 * focusDistance * f)); + double const fov = 2.0 * std::atan(FCamera::SENSOR_SIZE * (focusDistance - f) / (2.0 * focusDistance * f)); return fov * math::d::RAD_TO_DEG; } -template -math::details::TMat44 inverseProjection(const math::details::TMat44& p) noexcept { - math::details::TMat44 r; - const T A = 1 / p[0][0]; - const T B = 1 / p[1][1]; - if (p[2][3] != T(0)) { - // perspective projection - // a 0 tx 0 - // 0 b ty 0 - // 0 0 tz c - // 0 0 -1 0 - const T C = 1 / p[3][2]; - r[0][0] = A; - r[1][1] = B; - r[2][2] = 0; - r[2][3] = C; - r[3][0] = p[2][0] * A; // not needed if symmetric - r[3][1] = p[2][1] * B; // not needed if symmetric - r[3][2] = -1; - r[3][3] = p[2][2] * C; - } else { - // orthographic projection - // a 0 0 tx - // 0 b 0 ty - // 0 0 c tz - // 0 0 0 1 - const T C = 1 / p[2][2]; - r[0][0] = A; - r[1][1] = B; - r[2][2] = C; - r[3][3] = 1; - r[3][0] = -p[3][0] * A; - r[3][1] = -p[3][1] * B; - r[3][2] = -p[3][2] * C; - } - return r; -} - // ------------------------------------------------------------------------------------------------ CameraInfo::CameraInfo(FCamera const& camera) noexcept { diff --git a/filament/src/details/Camera.h b/filament/src/details/Camera.h index 4da42c6536f..26e1297c56a 100644 --- a/filament/src/details/Camera.h +++ b/filament/src/details/Camera.h @@ -46,31 +46,27 @@ class FCamera : public Camera { FCamera(FEngine& engine, utils::Entity e); - void terminate(FEngine& engine) noexcept { } + void terminate(FEngine&) noexcept { } - void setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model); - void setCustomEyeProjection(math::mat4 const* projection, size_t count, - math::mat4 const& projectionForCulling, double near, double far); - - // sets the projection matrix + // Sets the projection matrices (viewing and culling). The viewing matrice has infinite far. void setProjection(Projection projection, double left, double right, double bottom, double top, double near, double far); - // sets the projection matrix - void setProjection(double fovInDegrees, double aspect, double near, double far, - Fov direction = Fov::VERTICAL); - - // sets the projection matrix - void setLensProjection(double focalLengthInMillimeters, - double aspect, double near, double far); - - // Sets a custom projection matrix (sets both the viewing and culling projections). - void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept; + // Sets custom projection matrices (sets both the viewing and culling projections). void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, double near, double far) noexcept; + inline void setCustomProjection(math::mat4 const& projection, + double near, double far) noexcept { + setCustomProjection(projection, projection, near, far); + } + + void setCustomEyeProjection(math::mat4 const* projection, size_t count, + math::mat4 const& projectionForCulling, double near, double far); + + void setScaling(math::double2 scaling) noexcept { mScalingCS = scaling; } math::double4 getScaling() const noexcept { return math::double4{ mScalingCS, 1.0, 1.0 }; } @@ -105,6 +101,7 @@ class FCamera : public Camera { // sets the camera's model matrix (must be a rigid transform) void setModelMatrix(const math::mat4& modelMatrix) noexcept; void setModelMatrix(const math::mat4f& modelMatrix) noexcept; + void setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model); // sets the camera's model matrix void lookAt(math::double3 const& eye, math::double3 const& center, math::double3 const& up) noexcept; @@ -196,6 +193,12 @@ class FCamera : public Camera { return mEntity; } + static math::mat4 projection(Fov direction, double fovInDegrees, + double aspect, double near, double far); + + static math::mat4 projection(double focalLengthInMillimeters, + double aspect, double near, double far); + private: FEngine& mEngine; utils::Entity mEntity; @@ -238,7 +241,7 @@ struct CameraInfo { math::mat4f eyeFromView[CONFIG_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic) math::mat4 worldOrigin; // world origin transform (already applied // to model and view) - math::float4 clipTransfrom{1, 1, 0, 0}; // clip-space transform, only for VERTEX_DOMAIN_DEVICE + 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 float ev100{}; // exposure diff --git a/filament/src/details/DebugRegistry.cpp b/filament/src/details/DebugRegistry.cpp index 58363dac250..decd59610a9 100644 --- a/filament/src/details/DebugRegistry.cpp +++ b/filament/src/details/DebugRegistry.cpp @@ -16,6 +16,8 @@ #include "details/DebugRegistry.h" +#include + #include #include #include @@ -33,33 +35,53 @@ namespace filament { FDebugRegistry::FDebugRegistry() noexcept = default; -UTILS_NOINLINE -void* FDebugRegistry::getPropertyAddress(const char* name) noexcept { - std::string_view key{ name }; +auto FDebugRegistry::getPropertyInfo(const char* name) noexcept -> PropertyInfo { + std::string_view const key{ name }; auto& propertyMap = mPropertyMap; if (propertyMap.find(key) == propertyMap.end()) { - return nullptr; + return { nullptr, {} }; } return propertyMap[key]; } -void FDebugRegistry::registerProperty(std::string_view name, void* p, Type type) noexcept { +UTILS_NOINLINE +void* FDebugRegistry::getPropertyAddress(const char* name) { + auto info = getPropertyInfo(name); + ASSERT_PRECONDITION_NON_FATAL(!info.second, + "don't use DebugRegistry::getPropertyAddress() when a callback is set. " + "Use setProperty() instead."); + return info.first; +} + +UTILS_NOINLINE +void const* FDebugRegistry::getPropertyAddress(const char* name) const noexcept { + auto info = const_cast(this)->getPropertyInfo(name); + return info.first; +} + +void FDebugRegistry::registerProperty(std::string_view name, void* p, Type, + std::function fn) noexcept { auto& propertyMap = mPropertyMap; if (propertyMap.find(name) == propertyMap.end()) { - propertyMap[name] = p; + propertyMap[name] = { p, std::move(fn) }; } } bool FDebugRegistry::hasProperty(const char* name) const noexcept { - return const_cast(this)->getPropertyAddress(name) != nullptr; + return getPropertyAddress(name) != nullptr; } template bool FDebugRegistry::setProperty(const char* name, T v) noexcept { if constexpr (DEBUG_PROPERTIES_WRITABLE) { - T* const addr = static_cast(getPropertyAddress(name)); + auto info = getPropertyInfo(name); + T* const addr = static_cast(info.first); if (addr) { + auto old = *addr; *addr = v; + if (info.second && old != v) { + info.second(); + } return true; } } @@ -75,8 +97,7 @@ template bool FDebugRegistry::setProperty(const char* name, float4 v) no template bool FDebugRegistry::getProperty(const char* name, T* p) const noexcept { - FDebugRegistry* const pRegistry = const_cast(this); - T const* const addr = static_cast(pRegistry->getPropertyAddress(name)); + T const* const addr = static_cast(getPropertyAddress(name)); if (addr) { *p = *addr; return true; @@ -100,7 +121,7 @@ void FDebugRegistry::registerDataSource(std::string_view name, } DebugRegistry::DataSource FDebugRegistry::getDataSource(const char* name) const noexcept { - std::string_view key{ name }; + std::string_view const key{ name }; auto& dataSourceMap = mDataSourceMap; auto const& it = dataSourceMap.find(key); if (it == dataSourceMap.end()) { diff --git a/filament/src/details/DebugRegistry.h b/filament/src/details/DebugRegistry.h index d2d1fd2195d..44fc0818925 100644 --- a/filament/src/details/DebugRegistry.h +++ b/filament/src/details/DebugRegistry.h @@ -23,8 +23,10 @@ #include +#include #include #include +#include namespace filament { @@ -58,6 +60,37 @@ class FDebugRegistry : public DebugRegistry { registerProperty(name, p, FLOAT4); } + + void registerProperty(std::string_view name, bool* p, + std::function fn) noexcept { + registerProperty(name, p, BOOL, std::move(fn)); + } + + void registerProperty(std::string_view name, int* p, + std::function fn) noexcept { + registerProperty(name, p, INT, std::move(fn)); + } + + void registerProperty(std::string_view name, float* p, + std::function fn) noexcept { + registerProperty(name, p, FLOAT, std::move(fn)); + } + + void registerProperty(std::string_view name, math::float2* p, + std::function fn) noexcept { + registerProperty(name, p, FLOAT2, std::move(fn)); + } + + void registerProperty(std::string_view name, math::float3* p, + std::function fn) noexcept { + registerProperty(name, p, FLOAT3, std::move(fn)); + } + + void registerProperty(std::string_view name, math::float4* p, + std::function fn) noexcept { + registerProperty(name, p, FLOAT4, std::move(fn)); + } + void registerDataSource(std::string_view name, void const* data, size_t count) noexcept; #if !defined(_MSC_VER) @@ -67,12 +100,15 @@ class FDebugRegistry : public DebugRegistry { template bool setProperty(const char* name, T v) noexcept; private: + using PropertyInfo = std::pair>; friend class DebugRegistry; - void registerProperty(std::string_view name, void* p, Type type) noexcept; + void registerProperty(std::string_view name, void* p, Type type, std::function fn = {}) noexcept; bool hasProperty(const char* name) const noexcept; - void* getPropertyAddress(const char* name) noexcept; + PropertyInfo getPropertyInfo(const char* name) noexcept; + void* getPropertyAddress(const char* name); + void const* getPropertyAddress(const char* name) const noexcept; DataSource getDataSource(const char* name) const noexcept; - std::unordered_map mPropertyMap; + std::unordered_map mPropertyMap; std::unordered_map mDataSourceMap; }; diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 103c382b812..84bb55f4a0f 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -200,7 +200,7 @@ FEngine::FEngine(Engine::Builder const& builder) : "FEngine::mPerRenderPassAllocator", builder->mConfig.perRenderPassArenaSizeMB * MiB), mHeapAllocator("FEngine::mHeapAllocator", AreaPolicy::NullArea{}), - mJobSystem(getJobSystemThreadPoolSize()), + mJobSystem(getJobSystemThreadPoolSize(builder->mConfig)), mEngineEpoch(std::chrono::steady_clock::now()), mDriverBarrier(1), mMainThreadId(ThreadUtils::getThreadId()), @@ -214,7 +214,11 @@ FEngine::FEngine(Engine::Builder const& builder) : << "(threading is " << (UTILS_HAS_THREADING ? "enabled)" : "disabled)") << io::endl; } -uint32_t FEngine::getJobSystemThreadPoolSize() noexcept { +uint32_t FEngine::getJobSystemThreadPoolSize(Engine::Config const& config) noexcept { + if (config.jobSystemThreadCount > 0) { + return config.jobSystemThreadCount; + } + // 1 thread for the user, 1 thread for the backend int threadCount = (int)std::thread::hardware_concurrency() - 2; // make sure we have at least 1 thread though @@ -359,6 +363,28 @@ void FEngine::init() { mLightManager.init(*this); mDFG.init(*this); } + + mDebugRegistry.registerProperty("d.shadowmap.debug_directional_shadowmap", + &debug.shadowmap.debug_directional_shadowmap, [this]() { + mMaterials.forEach([](FMaterial* material) { + if (material->getMaterialDomain() == MaterialDomain::SURFACE) { + 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([](FMaterial* material) { + if (material->getMaterialDomain() == MaterialDomain::SURFACE) { + material->invalidate( + Variant::DYN | Variant::DEP, + Variant::DYN); + } + }); + }); } FEngine::~FEngine() noexcept { @@ -398,7 +424,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); @@ -511,7 +537,7 @@ void FEngine::gc() { mRenderableManager.gc(em); mLightManager.gc(em); mTransformManager.gc(em); - mCameraManager.gc(em); + mCameraManager.gc(*this, em); } void FEngine::flush() { @@ -802,7 +828,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 { @@ -811,7 +837,7 @@ FCamera* FEngine::getCameraComponent(Entity entity) noexcept { } void FEngine::destroyCameraComponent(utils::Entity entity) noexcept { - mCameraManager.destroy(entity); + mCameraManager.destroy(*this, entity); } @@ -1027,7 +1053,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) { diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index c6906193782..c6cfaee95f4 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -487,7 +487,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; @@ -508,7 +508,7 @@ class FEngine : public Engine { HeapAllocatorArena mHeapAllocator; utils::JobSystem mJobSystem; - static uint32_t getJobSystemThreadPoolSize() noexcept; + static uint32_t getJobSystemThreadPoolSize(Engine::Config const& config) noexcept; std::default_random_engine mRandomEngine; @@ -543,6 +543,7 @@ class FEngine : public Engine { // these are the debug properties used by FDebug. They're accessed directly by modules who need them. struct { struct { + bool debug_directional_shadowmap = false; bool far_uses_shadowcasters = true; bool focus_shadowcasters = true; bool visualize_cascades = false; @@ -564,6 +565,9 @@ class FEngine : public Engine { bool doFrameCapture = false; bool disable_buffer_padding = false; } renderer; + struct { + bool debug_froxel_visualization = false; + } lighting; matdbg::DebugServer* server = nullptr; } debug; }; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 6fcf25c2297..bd89267af6a 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -246,6 +246,12 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT, (int)maxFroxelBufferHeight }); + mSpecializationConstants.push_back({ + +ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP, + (bool)engine.debug.shadowmap.debug_directional_shadowmap }); + mSpecializationConstants.push_back({ + +ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION, + (bool)engine.debug.lighting.debug_froxel_visualization }); mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND, (bool)staticTextureWorkaround }); @@ -447,6 +453,48 @@ FMaterial::~FMaterial() noexcept { delete mMaterialParser; } +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? + for (auto& item : mSpecializationConstants) { + if (item.id == ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP) { + item.value = mEngine.debug.shadowmap.debug_directional_shadowmap; + } else if (item.id == ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION) { + item.value = mEngine.debug.lighting.debug_froxel_visualization; + } + } + + 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(); + } + } + + 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); + } + } + } +} + void FMaterial::terminate(FEngine& engine) { #if FILAMENT_ENABLE_MATDBG @@ -719,10 +767,7 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n void FMaterial::applyPendingEdits() noexcept { const char* name = mName.c_str(); slog.d << "Applying edits to " << (name ? name : "(untitled)") << io::endl; - destroyPrograms(mEngine); - for (auto& program : mCachedPrograms) { - program.clear(); - } + destroyPrograms(mEngine); // FIXME: this will not destroy the shared variants delete mMaterialParser; mMaterialParser = mPendingEdits; mPendingEdits = nullptr; @@ -772,6 +817,7 @@ void FMaterial::destroyPrograms(FEngine& engine) { } } driverApi.destroyProgram(cachedPrograms[k]); + cachedPrograms[k].clear(); } } diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index c0fdeb454cd..fc1c6a8102d 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -86,6 +86,8 @@ class FMaterial : public Material { return bool(mCachedPrograms[variant.key]); } + void invalidate(Variant::type_t variantMask = 0, Variant::type_t variantValue = 0) noexcept; + // prepareProgram creates the program for the material's given variant at the backend level. // Must be called outside of backend render pass. // Must be called before getProgram() below. diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index b069f14cb55..4ee8e2b2054 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -598,7 +598,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // VERTEX_DOMAIN_DEVICE doesn't apply the projection, but it still needs this // clip transform, so we apply it separately (see main.vs) - cameraInfo.clipTransfrom = { ts[0][0], ts[1][1], ts[3].x, ts[3].y }; + cameraInfo.clipTransform = { ts[0][0], ts[1][1], ts[3].x, ts[3].y }; // adjust svp to the new, larger, rendering dimensions svp.width = uint32_t(width); diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index ae6b3788a2e..2d08b6cfe0d 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -127,6 +127,10 @@ class FRenderer : public Renderer { mClearOptions = options; } + ClearOptions const& getClearOptions() const noexcept { + return mClearOptions; + } + private: friend class Renderer; using Command = RenderPass::Command; diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index d7adc8d3c80..90098617621 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -55,7 +55,7 @@ class FSkybox; class FScene : public Scene { public: /* - * Filaments-scope Public API + * Filament-scope Public API */ FSkybox* getSkybox() const noexcept { return mSkybox; } @@ -193,6 +193,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/View.cpp b/filament/src/details/View.cpp index 3485e3caf81..271c39a7100 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -768,6 +768,9 @@ void FView::prepareShadow(Handle texture) const noexcept { case filament::ShadowType::PCSS: mPerViewUniforms.prepareShadowPCSS(texture, uniforms, mSoftShadowOptions); break; + case filament::ShadowType::PCFd: + mPerViewUniforms.prepareShadowPCFDebug(texture, uniforms); + break; } } diff --git a/filament/src/details/View.h b/filament/src/details/View.h index f452ce22f18..50a6c6bb52e 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -73,8 +73,6 @@ class FMaterialInstance; class FRenderer; class FScene; -static constexpr Culler::result_type VISIBLE_RENDERABLE = 1u << VISIBLE_RENDERABLE_BIT; - // ------------------------------------------------------------------------------------------------ class FView : public View { diff --git a/filament/src/fg/FrameGraph.cpp b/filament/src/fg/FrameGraph.cpp index bd21dbb537e..ae8a5b8e920 100644 --- a/filament/src/fg/FrameGraph.cpp +++ b/filament/src/fg/FrameGraph.cpp @@ -61,7 +61,7 @@ FrameGraphId FrameGraph::Builder::declareRenderPass( FrameGraph::FrameGraph(ResourceAllocatorInterface& resourceAllocator) : mResourceAllocator(resourceAllocator), - mArena("FrameGraph Arena", 131072), + mArena("FrameGraph Arena", 262144), mResourceSlots(mArena), mResources(mArena), mResourceNodes(mArena), diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 723561db302..e6ca3b3dbed 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.45.0" + spec.version = "1.45.1" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.45.0/filament-v1.45.0-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.45.1/filament-v1.45.1-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index f72c2ac66ab..70d736cb4d3 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -64,6 +64,8 @@ enum class ReservedSpecializationConstants : uint8_t { CONFIG_SRGB_SWAPCHAIN_EMULATION = 3, // don't change (hardcoded in OpenGLDriver.cpp) CONFIG_FROXEL_BUFFER_HEIGHT = 4, CONFIG_POWER_VR_SHADER_WORKAROUNDS = 5, + CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP = 6, + CONFIG_DEBUG_FROXEL_VISUALIZATION = 7, }; // This value is limited by UBO size, ES3.0 only guarantees 16 KiB. diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index fbb3473012e..db36aae23d9 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -578,13 +578,14 @@ std::shared_ptr GLSLPostProcessor::createOptimizer( registerSizePasses(*optimizer, config); } else if (optimization == MaterialBuilder::Optimization::PERFORMANCE) { registerPerformancePasses(*optimizer, config); - // Metal doesn't support relaxed precision, but does have support for float16 math operations. - if (config.targetApi == MaterialBuilder::TargetApi::METAL) { - optimizer->RegisterPass(CreateConvertRelaxedToHalfPass()); - optimizer->RegisterPass(CreateSimplificationPass()); - optimizer->RegisterPass(CreateRedundancyEliminationPass()); - optimizer->RegisterPass(CreateAggressiveDCEPass()); - } + } + + // Metal doesn't support relaxed precision, but does have support for float16 math operations. + if (config.targetApi == MaterialBuilder::TargetApi::METAL) { + optimizer->RegisterPass(CreateConvertRelaxedToHalfPass()); + optimizer->RegisterPass(CreateSimplificationPass()); + optimizer->RegisterPass(CreateRedundancyEliminationPass()); + optimizer->RegisterPass(CreateAggressiveDCEPass()); } return optimizer; diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 8c58366b29a..dc9d2150341 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -234,6 +234,14 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade +ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT, 1024); } + // directional shadowmap visualization + 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", diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 17804511a58..51ba611d14a 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -70,24 +70,30 @@ using namespace filament::backend; #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) class FilamentAppVulkanPlatform : public VulkanPlatform { public: - FilamentAppVulkanPlatform(std::string const& gpuHint) { + FilamentAppVulkanPlatform(char const* gpuHintCstr) { + utils::CString gpuHint{ gpuHintCstr }; if (gpuHint.empty()) { return; } + VulkanPlatform::Customization::GPUPreference pref; // Check to see if it is an integer, if so turn it into an index. if (std::all_of(gpuHint.begin(), gpuHint.end(), ::isdigit)) { - mPreference.index = static_cast(std::stoi(gpuHint)); - return; + char* p_end {}; + pref.index = static_cast(std::strtol(gpuHint.c_str(), &p_end, 10)); + } else { + pref.deviceName = gpuHint; } - mPreference.deviceName = gpuHint; + mCustomization = { + .gpu = pref + }; } - virtual VulkanPlatform::GPUPreference getPreferredGPU() noexcept override { - return mPreference; + virtual VulkanPlatform::Customization getCustomization() const noexcept override { + return mCustomization; } private: - VulkanPlatform::GPUPreference mPreference; + VulkanPlatform::Customization mCustomization; }; #endif @@ -592,7 +598,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, if (backend == Engine::Backend::VULKAN) { #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) - mFilamentApp->mVulkanPlatform = new FilamentAppVulkanPlatform(config.vulkanGPUHint); + mFilamentApp->mVulkanPlatform = + new FilamentAppVulkanPlatform(config.vulkanGPUHint.c_str()); return Engine::Builder() .backend(backend) .platform(mFilamentApp->mVulkanPlatform) diff --git a/libs/gltfio/CMakeLists.txt b/libs/gltfio/CMakeLists.txt index b4856b29413..d570f61e8e7 100644 --- a/libs/gltfio/CMakeLists.txt +++ b/libs/gltfio/CMakeLists.txt @@ -123,12 +123,12 @@ build_ubershader(specularGlossiness_masked base specularGlossiness ma build_ubershader(unlit_fade base unlit fade) build_ubershader(unlit_opaque base unlit opaque) build_ubershader(unlit_masked base unlit masked) -build_ubershader(volume_opaque volume lit opaque) -build_ubershader(volume_fade volume lit fade) -build_ubershader(volume_masked volume lit masked) build_ubershader(transmission_opaque transmission lit opaque) build_ubershader(transmission_fade transmission lit fade) build_ubershader(transmission_masked transmission lit masked) +build_ubershader(volume_opaque volume lit opaque) +build_ubershader(volume_fade volume lit fade) +build_ubershader(volume_masked volume lit masked) build_ubershader(sheen sheen _ _) add_custom_target(uberz_file DEPENDS ${UBERZ_OUTPUT_PATH}) diff --git a/libs/gltfio/include/gltfio/AssetLoader.h b/libs/gltfio/include/gltfio/AssetLoader.h index c031850f3b6..080538aa336 100644 --- a/libs/gltfio/include/gltfio/AssetLoader.h +++ b/libs/gltfio/include/gltfio/AssetLoader.h @@ -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/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 3fd23a40a1d..3cb8840656a 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -228,9 +228,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 +256,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 +299,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 +341,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 +349,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 +473,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 +496,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 +532,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 +571,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 +595,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) { + const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy; const cgltf_mesh* mesh = node->mesh; + assert_invariant(srcAsset != 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 - srcAsset->meshes]; if (prims.empty()) { prims.reserve(mesh->primitives_count); prims.resize(mesh->primitives_count); @@ -642,7 +637,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 +651,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 +694,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 +766,9 @@ 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) { + const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy; UvMap uvmap {}; for (cgltf_size prim = 0, n = mesh->primitives_count; prim < n; ++prim) { const cgltf_primitive& srcPrim = mesh->primitives[prim]; @@ -780,21 +777,21 @@ 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 +801,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 +841,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 +849,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 = fAsset->mBufferSlots.size(); int slot = 0; for (cgltf_size aindex = 0; aindex < inPrim.attributes_count; aindex++) { @@ -872,7 +869,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; } @@ -958,7 +955,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 +1062,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 < fAsset->mBufferSlots.size(); ++i) { + fAsset->mBufferSlots[i].vertexBuffer = vertices; } if (targetsCount > 0) { @@ -1078,7 +1075,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, Primitive* out .count(targetsCount) .build(mEngine); outPrim->targets = targets; - mAsset->mMorphTargetBuffers.push_back(targets); + fAsset->mMorphTargetBuffers.push_back(targets); const cgltf_accessor* previous = nullptr; for (int tindex = 0; tindex < targetsCount; ++tindex) { const cgltf_morph_target& inTarget = inPrim.targets[tindex]; @@ -1104,7 +1101,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 +1113,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 +1146,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 +1184,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 +1298,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 +1366,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 +1380,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 +1389,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 +1401,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 +1413,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 +1426,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 +1435,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 +1444,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 +1461,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 +1471,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 +1492,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 +1505,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 +1538,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/FFilamentAsset.h b/libs/gltfio/src/FFilamentAsset.h index 245e5f0f05f..dc516f90d13 100644 --- a/libs/gltfio/src/FFilamentAsset.h +++ b/libs/gltfio/src/FFilamentAsset.h @@ -290,6 +290,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/matdbg/CMakeLists.txt b/libs/matdbg/CMakeLists.txt index f53413be05f..0dafe5c71dd 100644 --- a/libs/matdbg/CMakeLists.txt +++ b/libs/matdbg/CMakeLists.txt @@ -22,6 +22,8 @@ set(PUBLIC_HDRS ) set(SRCS + src/ApiHandler.cpp + src/ApiHandler.h src/CommonWriter.h src/CommonWriter.cpp src/DebugServer.cpp @@ -59,7 +61,6 @@ endif() set(DUMMY_SRC "${RESOURCE_DIR}/dummy.c") add_custom_command(OUTPUT ${DUMMY_SRC} COMMAND echo "//" > ${DUMMY_SRC}) - add_library(matdbg_resources ${DUMMY_SRC} ${RESGEN_SOURCE}) set_target_properties(matdbg_resources PROPERTIES FOLDER Libs) diff --git a/libs/matdbg/include/matdbg/DebugServer.h b/libs/matdbg/include/matdbg/DebugServer.h index fa1ef0fc9b4..4340001d661 100644 --- a/libs/matdbg/include/matdbg/DebugServer.h +++ b/libs/matdbg/include/matdbg/DebugServer.h @@ -24,23 +24,34 @@ #include #include +#include class CivetServer; -namespace filament { -namespace matdbg { +namespace filament::matdbg { using MaterialKey = uint32_t; +struct MaterialRecord { + void* userdata; + const uint8_t* package; + size_t packageSize; + utils::CString name; + MaterialKey key; + VariantList activeVariants; +}; + /** * Server-side material debugger. * - * This class manages an HTTP server and a WebSockets server that listen on a secondary thread. It - * receives material packages from the Filament C++ engine or from a standalone tool such as - * matinfo. + * This class manages an HTTP server. It receives material packages from the Filament C++ engine or + * from a standalone tool such as matinfo. */ class DebugServer { public: + static std::string_view const kSuccessHeader; + static std::string_view const kErrorHeader; + DebugServer(backend::Backend backend, int port); ~DebugServer(); @@ -74,16 +85,7 @@ class DebugServer { bool isReady() const { return mServer; } private: - struct MaterialRecord { - void* userdata; - const uint8_t* package; - size_t packageSize; - utils::CString name; - MaterialKey key; - VariantList activeVariants; - }; - - const MaterialRecord* getRecord(const MaterialKey& key) const; + MaterialRecord const* getRecord(const MaterialKey& key) const; void updateActiveVariants(); @@ -97,7 +99,10 @@ class DebugServer { const backend::Backend mBackend; CivetServer* mServer; + tsl::robin_map mMaterialRecords; + mutable utils::Mutex mMaterialRecordsMutex; + utils::CString mHtml; utils::CString mJavascript; utils::CString mCss; @@ -109,15 +114,12 @@ class DebugServer { QueryCallback mQueryCallback = nullptr; class FileRequestHandler* mFileHandler = nullptr; - class RestRequestHandler* mRestHandler = nullptr; - class WebSocketHandler* mWebSocketHandler = nullptr; + class ApiHandler* mApiHandler = nullptr; friend class FileRequestHandler; - friend class RestRequestHandler; - friend class WebSocketHandler; + friend class ApiHandler; }; -} // namespace matdbg -} // namespace filament +} // namespace filament::matdbg #endif // MATDBG_DEBUGSERVER_H diff --git a/libs/matdbg/include/matdbg/ShaderExtractor.h b/libs/matdbg/include/matdbg/ShaderExtractor.h index 0d1848de907..3669b50bf4d 100644 --- a/libs/matdbg/include/matdbg/ShaderExtractor.h +++ b/libs/matdbg/include/matdbg/ShaderExtractor.h @@ -31,7 +31,7 @@ namespace matdbg { // in a manner similar to ShaderReplacer. class ShaderExtractor { public: - ShaderExtractor(backend::Backend backend, const void* data, size_t size); + ShaderExtractor(backend::ShaderLanguage target, const void* data, size_t size); bool parse() noexcept; bool getShader(backend::ShaderModel shaderModel, Variant variant, backend::ShaderStage stage, filaflat::ShaderContent& shader) noexcept; diff --git a/libs/matdbg/include/matdbg/ShaderInfo.h b/libs/matdbg/include/matdbg/ShaderInfo.h index fc571c734ab..89d5a8377a4 100644 --- a/libs/matdbg/include/matdbg/ShaderInfo.h +++ b/libs/matdbg/include/matdbg/ShaderInfo.h @@ -35,9 +35,7 @@ struct ShaderInfo { }; size_t getShaderCount(const filaflat::ChunkContainer& container, filamat::ChunkType type); -bool getMetalShaderInfo(const filaflat::ChunkContainer& container, ShaderInfo* info); -bool getGlShaderInfo(const filaflat::ChunkContainer& container, ShaderInfo* info); -bool getVkShaderInfo(const filaflat::ChunkContainer& container, ShaderInfo* info); +bool getShaderInfo(const filaflat::ChunkContainer& container, ShaderInfo* info, filamat::ChunkType chunkType); } // namespace matdbg } // namespace filament diff --git a/libs/matdbg/src/ApiHandler.cpp b/libs/matdbg/src/ApiHandler.cpp new file mode 100644 index 00000000000..f2fa4b59963 --- /dev/null +++ b/libs/matdbg/src/ApiHandler.cpp @@ -0,0 +1,400 @@ +/* + * 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 "ApiHandler.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +namespace filament::matdbg { + +using namespace filament::backend; +using namespace std::chrono_literals; + +namespace { + +auto const& kSuccessHeader = DebugServer::kSuccessHeader; +auto const& kErrorHeader = DebugServer::kErrorHeader; + +void spirvToAsm(struct mg_connection* conn, uint32_t const* spirv, size_t size) { + auto spirvDisassembly = ShaderExtractor::spirvToText(spirv, size / 4); + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + mg_write(conn, spirvDisassembly.c_str(), spirvDisassembly.size()); +} + +void spirvToGlsl(ShaderModel shaderModel, struct mg_connection* conn, uint32_t const* spirv, + size_t size) { + auto glsl = ShaderExtractor::spirvToGLSL(shaderModel, spirv, size / 4); + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + mg_printf(conn, glsl.c_str(), glsl.size()); +} + +} // anonymous + +using filaflat::ChunkContainer; +using filamat::ChunkType; +using utils::FixedCapacityVector; + +static auto const error = [](int line, std::string const& uri) { + utils::slog.e << "DebugServer: 404 at line " << line << ": " << uri << utils::io::endl; + return false; +}; + +MaterialRecord const* ApiHandler::getMaterialRecord(struct mg_request_info const* request) { + size_t const qlength = strlen(request->query_string); + char matid[9] = {}; + if (mg_get_var(request->query_string, qlength, "matid", matid, sizeof(matid)) < 0) { + return nullptr; + } + uint32_t const id = strtoul(matid, nullptr, 16); + return mServer->getRecord(id); +} + +bool ApiHandler::handleGetApiShader(struct mg_connection* conn, + struct mg_request_info const* request) { + auto const softError = [conn, request](char const* msg) { + utils::slog.e << "DebugServer: " << msg << ": " << request->query_string << utils::io::endl; + mg_printf(conn, kErrorHeader.data(), "application/txt"); + mg_write(conn, msg, strlen(msg)); + return true; + }; + + MaterialRecord const* result = getMaterialRecord(request); + std::string const& uri = request->local_uri; + + if (!result) { + return error(__LINE__, uri); + } + + ChunkContainer package(result->package, result->packageSize); + if (!package.parse()) { + return error(__LINE__, uri); + } + + std::string_view const glsl("glsl"); + std::string_view const msl("msl"); + std::string_view const spirv("spirv"); + size_t const qlength = strlen(request->query_string); + + char type[6] = {}; + if (mg_get_var(request->query_string, qlength, "type", type, sizeof(type)) < 0) { + return error(__LINE__, uri); + } + + std::string_view const language(type, strlen(type)); + + char glindex[4] = {}; + char vkindex[4] = {}; + char metalindex[4] = {}; + mg_get_var(request->query_string, qlength, "glindex", glindex, sizeof(glindex)); + mg_get_var(request->query_string, qlength, "vkindex", vkindex, sizeof(vkindex)); + mg_get_var(request->query_string, qlength, "metalindex", metalindex, sizeof(metalindex)); + + if (!glindex[0] && !vkindex[0] && !metalindex[0]) { + return error(__LINE__, uri); + } + + if (glindex[0]) { + if (language != glsl) { + return softError("Only GLSL is supported."); + } + + FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialGlsl)); + if (!getShaderInfo(package, info.data(), ChunkType::MaterialGlsl)) { + return error(__LINE__, uri); + } + + int const shaderIndex = std::stoi(glindex); + if (shaderIndex >= info.size()) { + return error(__LINE__, uri); + } + + ShaderExtractor extractor(ShaderLanguage::ESSL3, result->package, result->packageSize); + if (!extractor.parse()) { + return error(__LINE__, uri); + } + + auto const& item = info[shaderIndex]; + filaflat::ShaderContent content; + extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content); + + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + mg_write(conn, content.data(), content.size() - 1); + return true; + } + + if (vkindex[0]) { + ShaderExtractor extractor(ShaderLanguage::SPIRV, result->package, result->packageSize); + if (!extractor.parse()) { + return error(__LINE__, uri); + } + + FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialSpirv)); + if (!getShaderInfo(package, info.data(), ChunkType::MaterialSpirv)) { + return error(__LINE__, uri); + } + + int const shaderIndex = std::stoi(vkindex); + if (shaderIndex >= info.size()) { + return error(__LINE__, uri); + } + + auto const& item = info[shaderIndex]; + filaflat::ShaderContent content; + extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content); + + if (language == spirv) { + spirvToAsm(conn, (uint32_t const*) content.data(), content.size()); + return true; + } + + if (language == glsl) { + spirvToGlsl(item.shaderModel, conn, (uint32_t const*) content.data(), content.size()); + return true; + } + + return softError("Only SPIRV is supported."); + } + + if (metalindex[0]) { + ShaderExtractor extractor(ShaderLanguage::MSL, result->package, result->packageSize); + if (!extractor.parse()) { + return error(__LINE__, uri); + } + + FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialMetal)); + if (!getShaderInfo(package, info.data(), ChunkType::MaterialMetal)) { + return error(__LINE__, uri); + } + + int const shaderIndex = std::stoi(metalindex); + if (shaderIndex >= info.size()) { + return error(__LINE__, uri); + } + + auto const& item = info[shaderIndex]; + filaflat::ShaderContent content; + extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content); + + if (language == msl) { + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + mg_write(conn, content.data(), content.size() - 1); + return true; + } + + return softError("Only MSL is supported."); + } + return error(__LINE__, uri); +} + +void ApiHandler::addMaterial(MaterialRecord const* material) { + std::unique_lock lock(mStatusMutex); + snprintf(statusMaterialId, sizeof(statusMaterialId), "%8.8x", material->key); + mStatusCondition.notify_all(); +} + +bool ApiHandler::handleGetStatus(struct mg_connection* conn, + struct mg_request_info const* request) { + char const* qstr = request->query_string; + if (qstr && strcmp(qstr, "firstTime") == 0) { + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + mg_write(conn, "0", 1); + return true; + } + + 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; +} + +bool ApiHandler::handlePost(CivetServer* server, struct mg_connection* conn) { + struct mg_request_info const* request = mg_get_request_info(conn); + std::string const& uri = request->local_uri; + + // For now we simply use istringstream for parsing, so command arguments are delimited + // with space characters. + // + // The "shader index" is a zero-based index into the list of variants using the order that + // they appear in the package, where each API (GL / VK / Metal) has its own list. + // + // POST body format: + // [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; + + char buf[1024]; + size_t readLen = 0; + std::stringstream sstream; + while (readLen < msgLen) { + int res = mg_read(conn, buf, sizeof(buf)); + if (res < 0) { + utils::slog.e << "civet error parsing /api/edit body: " << res << utils::io::endl; + break; + } + if (res == 0) { + break; + } + readLen += res; + sstream.write(buf, res); + } + uint32_t matid; + int api; + int shaderIndex; + sstream >> std::hex >> matid >> std::dec >> api >> shaderIndex; + std::string shader = sstream.str().substr(sstream.tellg()); + + mServer->handleEditCommand(matid, backend::Backend(api), shaderIndex, shader.c_str(), + shader.size()); + + mg_printf(conn, "HTTP/1.1 200 OK\r\nConnection: close"); + return true; + } + return error(__LINE__, uri); +} + +bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) { + struct mg_request_info const* request = mg_get_request_info(conn); + std::string const& uri = request->local_uri; + + if (uri == "/api/active") { + mServer->updateActiveVariants(); + + // Careful not to lock the above line. + std::unique_lock lock(mServer->mMaterialRecordsMutex); + mg_printf(conn, kSuccessHeader.data(), "application/json"); + mg_printf(conn, "{"); + + // If the backend has not been resolved to Vulkan, Metal, etc., then return an empty + // list. This can occur if the server is matinfo rather than an actual Filament session. + if (mServer->mBackend == backend::Backend::DEFAULT) { + mg_printf(conn, "}"); + return true; + } + + int index = 0; + for (auto const& pair: mServer->mMaterialRecords) { + auto const& record = pair.second; + ChunkContainer package(record.package, record.packageSize); + if (!package.parse()) { + return error(__LINE__, uri); + } + JsonWriter writer; + if (!writer.writeActiveInfo(package, mServer->mBackend, record.activeVariants)) { + return error(__LINE__, uri); + } + bool const last = (++index) == mServer->mMaterialRecords.size(); + mg_printf(conn, "\"%8.8x\": %s %s", pair.first, writer.getJsonString(), + last ? "" : ","); + } + mg_printf(conn, "}"); + return true; + } + + if (uri == "/api/matids") { + std::unique_lock lock(mServer->mMaterialRecordsMutex); + mg_printf(conn, kSuccessHeader.data(), "application/json"); + mg_printf(conn, "["); + int index = 0; + for (auto const& record: mServer->mMaterialRecords) { + bool const last = (++index) == mServer->mMaterialRecords.size(); + mg_printf(conn, "\"%8.8x\" %s", record.first, last ? "" : ","); + } + mg_printf(conn, "]"); + return true; + } + + if (uri == "/api/materials") { + std::unique_lock lock(mServer->mMaterialRecordsMutex); + mg_printf(conn, kSuccessHeader.data(), "application/json"); + mg_printf(conn, "["); + int index = 0; + for (auto const& record: mServer->mMaterialRecords) { + bool const last = (++index) == mServer->mMaterialRecords.size(); + + ChunkContainer package(record.second.package, record.second.packageSize); + if (!package.parse()) { + return error(__LINE__, uri); + } + + JsonWriter writer; + if (!writer.writeMaterialInfo(package)) { + return error(__LINE__, uri); + } + + mg_printf(conn, "{ \"matid\": \"%8.8x\", %s } %s", record.first, writer.getJsonString(), + last ? "" : ","); + } + mg_printf(conn, "]"); + return true; + } + + if (uri == "/api/material") { + MaterialRecord const* result = getMaterialRecord(request); + if (!result) { + return error(__LINE__, uri); + } + + ChunkContainer package(result->package, result->packageSize); + if (!package.parse()) { + return error(__LINE__, uri); + } + + JsonWriter writer; + if (!writer.writeMaterialInfo(package)) { + return error(__LINE__, uri); + } + mg_printf(conn, kSuccessHeader.data(), "application/json"); + mg_printf(conn, "{ %s }", writer.getJsonString()); + return true; + } + + if (uri == "/api/shader") { + return handleGetApiShader(conn, request); + } + + if (uri.find("/api/status") == 0) { + return handleGetStatus(conn, request); + } + + return error(__LINE__, uri); +} + +} // filament::matdbg diff --git a/libs/matdbg/src/ApiHandler.h b/libs/matdbg/src/ApiHandler.h new file mode 100644 index 00000000000..27464d56267 --- /dev/null +++ b/libs/matdbg/src/ApiHandler.h @@ -0,0 +1,68 @@ +/* + * 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 MATDBG_APIHANDLER_H +#define MATDBG_APIHANDLER_H + +#include + +#include + +namespace filament::matdbg { + +class DebugServer; +struct MaterialRecord; + +// Handles the following REST requests, where {id} is an 8-digit hex string. +// +// GET /api/matids +// GET /api/materials +// GET /api/material?matid={id} +// GET /api/shader?matid={id}&type=[glsl|spirv]&[glindex|vkindex|metalindex]={index} +// GET /api/active +// GET /api/status +// POST /api/edit +// +class ApiHandler : public CivetHandler { +public: + explicit ApiHandler(DebugServer* server) + : mServer(server) {} + ~ApiHandler() = default; + + bool handleGet(CivetServer* server, struct mg_connection* conn); + bool handlePost(CivetServer* server, struct mg_connection* conn); + void addMaterial(MaterialRecord const* material); + +private: + bool handleGetApiShader(struct mg_connection* conn, struct mg_request_info const* request); + bool handleGetStatus(struct mg_connection* conn, struct mg_request_info const* request); + MaterialRecord const* getMaterialRecord(struct mg_request_info const* request); + + DebugServer* mServer; + + utils::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; +}; + +} // filament::matdbg + +#endif // MATDBG_APIHANDLER_H diff --git a/libs/matdbg/src/DebugServer.cpp b/libs/matdbg/src/DebugServer.cpp index 1ccd12bf204..d5164a90c1b 100644 --- a/libs/matdbg/src/DebugServer.cpp +++ b/libs/matdbg/src/DebugServer.cpp @@ -16,6 +16,8 @@ #include +#include "ApiHandler.h" + #include #include @@ -48,12 +50,15 @@ using utils::FixedCapacityVector; // serves files directly from the source code tree. #define SERVE_FROM_SOURCE_TREE 0 +// When set to 1, we will serve an experimental frontend, which will potentially replace the current +// frontend when ready. +#define EXPERIMENTAL_WEB_FRAMEWORK 0 + #if !SERVE_FROM_SOURCE_TREE #include "matdbg_resources.h" #endif -namespace filament { -namespace matdbg { +namespace filament::matdbg { using namespace utils; using namespace filament::backend; @@ -61,31 +66,50 @@ using namespace filament::backend; using filaflat::ChunkContainer; using filamat::ChunkType; -static const std::string_view kSuccessHeader = +std::string_view const DebugServer::kSuccessHeader = "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n" "Connection: close\r\n\r\n"; -static const std::string_view kErrorHeader = +std::string_view const DebugServer::kErrorHeader = "HTTP/1.1 404 Not Found\r\nContent-Type: %s\r\n" "Connection: close\r\n\r\n"; -static void spirvToAsm(struct mg_connection *conn, const uint32_t* spirv, size_t size) { - auto spirvDisassembly = ShaderExtractor::spirvToText(spirv, size / 4); - mg_printf(conn, kSuccessHeader.data(), "application/txt"); - mg_write(conn, spirvDisassembly.c_str(), spirvDisassembly.size()); -} +#if EXPERIMENTAL_WEB_FRAMEWORK -static void spirvToGlsl(ShaderModel shaderModel, struct mg_connection *conn, - const uint32_t* spirv, size_t size) { - auto glsl = ShaderExtractor::spirvToGLSL(shaderModel, spirv, size / 4); - mg_printf(conn, kSuccessHeader.data(), "application/txt"); - mg_printf(conn, glsl.c_str(), glsl.size()); -} +namespace { + +std::string const BASE_URL = "libs/matdbg/web/experiment"; + +} // anonymous class FileRequestHandler : public CivetHandler { public: FileRequestHandler(DebugServer* server) : mServer(server) {} bool handleGet(CivetServer *server, struct mg_connection *conn) { + auto const& kSuccessHeader = DebugServer::kSuccessHeader; + struct mg_request_info const* request = mg_get_request_info(conn); + std::string uri(request->request_uri); + if (uri == "/") { + uri = "/index.html"; + } + if (uri == "/index.html" || uri == "/app.js" || uri == "/api.js") { + mg_send_file(conn, (BASE_URL + uri).c_str()); + return true; + } + slog.e << "DebugServer: bad request at line " << __LINE__ << ": " << uri << io::endl; + return false; + } +private: + DebugServer* mServer; +}; + +#else +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); std::string uri(request->request_uri); if (uri == "/" || uri == "/index.html") { @@ -121,367 +145,7 @@ class FileRequestHandler : public CivetHandler { private: DebugServer* mServer; }; - -// Handles the following REST requests, where {id} is an 8-digit hex string. -// -// GET /api/matids -// GET /api/materials -// GET /api/material?matid={id} -// GET /api/shader?matid={id}&type=[glsl|spirv]&[glindex|vkindex|metalindex]={index} -// GET /api/active -// -class RestRequestHandler : public CivetHandler { -public: - RestRequestHandler(DebugServer* server) : mServer(server) {} - - bool handleGet(CivetServer *server, struct mg_connection *conn) { - const struct mg_request_info* request = mg_get_request_info(conn); - std::string uri(request->local_uri); - - const auto error = [request](int line) { - slog.e << "DebugServer: 404 at line " << line << ": " << request->local_uri - << io::endl; - return false; - }; - - const auto softError = [request, conn](const char* msg) { - slog.e << "DebugServer: " << msg << ": " << request->query_string << io::endl; - mg_printf(conn, kErrorHeader.data(), "application/txt"); - mg_write(conn, msg, strlen(msg)); - return true; - }; - - if (uri == "/api/active") { - mServer->updateActiveVariants(); - mg_printf(conn, kSuccessHeader.data(), "application/json"); - mg_printf(conn, "{"); - - // If the backend has not been resolved to Vulkan, Metal, etc., then return an empty - // list. This can occur if the server is matinfo rather than an actual Filament session. - if (mServer->mBackend == backend::Backend::DEFAULT) { - mg_printf(conn, "}"); - return true; - } - - int index = 0; - for (const auto& pair : mServer->mMaterialRecords) { - const auto& record = pair.second; - ChunkContainer package(record.package, record.packageSize); - if (!package.parse()) { - return error(__LINE__); - } - JsonWriter writer; - if (!writer.writeActiveInfo(package, mServer->mBackend, record.activeVariants)) { - return error(__LINE__); - } - const bool last = (++index) == mServer->mMaterialRecords.size(); - mg_printf(conn, "\"%8.8x\": %s %s", pair.first, writer.getJsonString(), - last ? "" : ","); - } - mg_printf(conn, "}"); - return true; - } - - if (uri == "/api/matids") { - mg_printf(conn, kSuccessHeader.data(), "application/json"); - mg_printf(conn, "["); - int index = 0; - for (const auto& record : mServer->mMaterialRecords) { - const bool last = (++index) == mServer->mMaterialRecords.size(); - mg_printf(conn, "\"%8.8x\" %s", record.first, last ? "" : ","); - } - mg_printf(conn, "]"); - return true; - } - - if (uri == "/api/materials") { - mg_printf(conn, kSuccessHeader.data(), "application/json"); - mg_printf(conn, "["); - int index = 0; - for (const auto& record : mServer->mMaterialRecords) { - const bool last = (++index) == mServer->mMaterialRecords.size(); - - ChunkContainer package(record.second.package, record.second.packageSize); - if (!package.parse()) { - return error(__LINE__); - } - - JsonWriter writer; - if (!writer.writeMaterialInfo(package)) { - return error(__LINE__); - } - - mg_printf(conn, "{ \"matid\": \"%8.8x\", %s } %s", record.first, - writer.getJsonString(), last ? "" : ","); - } - mg_printf(conn, "]"); - return true; - } - - if (!request->query_string) { - return error(__LINE__); - } - - const size_t qlength = strlen(request->query_string); - char matid[9] = {}; - if (mg_get_var(request->query_string, qlength, "matid", matid, sizeof(matid)) < 0) { - return error(__LINE__); - } - const uint32_t id = strtoul(matid, nullptr, 16); - const DebugServer::MaterialRecord* result = mServer->getRecord(id); - if (result == nullptr) { - return error(__LINE__); - } - - ChunkContainer package(result->package, result->packageSize); - if (!package.parse()) { - return error(__LINE__); - } - - if (uri == "/api/material") { - JsonWriter writer; - if (!writer.writeMaterialInfo(package)) { - return error(__LINE__); - } - mg_printf(conn, kSuccessHeader.data(), "application/json"); - mg_printf(conn, "{ %s }", writer.getJsonString()); - return true; - } - - const std::string_view glsl("glsl"); - const std::string_view msl("msl"); - const std::string_view spirv("spirv"); - - char type[6] = {}; - if (mg_get_var(request->query_string, qlength, "type", type, sizeof(type)) < 0) { - return error(__LINE__); - } - - std::string_view const language(type, strlen(type)); - - char glindex[4] = {}; - char vkindex[4] = {}; - char metalindex[4] = {}; - mg_get_var(request->query_string, qlength, "glindex", glindex, sizeof(glindex)); - mg_get_var(request->query_string, qlength, "vkindex", vkindex, sizeof(vkindex)); - mg_get_var(request->query_string, qlength, "metalindex", metalindex, sizeof(metalindex)); - - if (!glindex[0] && !vkindex[0] && !metalindex[0]) { - return error(__LINE__); - } - - if (uri != "/api/shader") { - return error(__LINE__); - } - - if (glindex[0]) { - if (language != glsl) { - return softError("Only GLSL is supported."); - } - - FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialGlsl)); - if (!getGlShaderInfo(package, info.data())) { - return error(__LINE__); - } - - const int shaderIndex = std::stoi(glindex); - if (shaderIndex >= info.size()) { - return error(__LINE__); - } - - ShaderExtractor extractor(Backend::OPENGL, result->package, result->packageSize); - if (!extractor.parse()) { - return error(__LINE__); - } - - const auto& item = info[shaderIndex]; - filaflat::ShaderContent content; - extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content); - - mg_printf(conn, kSuccessHeader.data(), "application/txt"); - mg_write(conn, content.data(), content.size() - 1); - return true; - } - - if (vkindex[0]) { - ShaderExtractor extractor(Backend::VULKAN, result->package, result->packageSize); - if (!extractor.parse()) { - return error(__LINE__); - } - - filaflat::ShaderContent content; - FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialSpirv)); - if (!getVkShaderInfo(package, info.data())) { - return error(__LINE__); - } - - const int shaderIndex = std::stoi(vkindex); - if (shaderIndex >= info.size()) { - return error(__LINE__); - } - - const auto& item = info[shaderIndex]; - extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content); - - if (language == spirv) { - spirvToAsm(conn, (const uint32_t*) content.data(), content.size()); - return true; - } - - if (language == glsl) { - spirvToGlsl(item.shaderModel, conn, (const uint32_t*) content.data(), content.size()); - return true; - } - - return softError("Only SPIRV is supported."); - } - - if (metalindex[0]) { - ShaderExtractor extractor(Backend::METAL, result->package, result->packageSize); - if (!extractor.parse()) { - return error(__LINE__); - } - - filaflat::ShaderContent content; - FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialMetal)); - if (!getMetalShaderInfo(package, info.data())) { - return error(__LINE__); - } - - const int shaderIndex = std::stoi(metalindex); - if (shaderIndex >= info.size()) { - return error(__LINE__); - } - - const auto& item = info[shaderIndex]; - extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content); - - if (language == msl) { - mg_printf(conn, kSuccessHeader.data(), "application/txt"); - mg_write(conn, content.data(), content.size() - 1); - return true; - } - - return softError("Only MSL is supported."); - } - - return error(__LINE__); - } - -private: - DebugServer* mServer; -}; - -class WebSocketHandler : public CivetWebSocketHandler { -public: - WebSocketHandler(DebugServer* server) : mServer(server) {} - - bool handleConnection(CivetServer *server, const struct mg_connection *conn) override { - return true; - } - - void handleReadyState(CivetServer *server, struct mg_connection *conn) override { - mConnections.insert(conn); - } - - bool handleData(CivetServer *server, struct mg_connection *conn, int bits, char *data, - size_t size) override { - - // First check if this chunk is a continuation of a partial existing message. - if (mServer->mChunkedMessageRemaining > 0) { - const CString chunk(data, size); - const size_t pos = mServer->mChunkedMessage.size(); - - // Append the partial existing message. - mServer->mChunkedMessage = mServer->mChunkedMessage.insert(pos, chunk); - - // Determine number of outstanding bytes. - if (size > mServer->mChunkedMessageRemaining) { - mServer->mChunkedMessageRemaining = 0; - } else { - mServer->mChunkedMessageRemaining -= size; - } - - // Return early and wait for more chunks if some bytes are still outstanding. - if (mServer->mChunkedMessageRemaining > 0) { - return true; - } - - data = mServer->mChunkedMessage.data(); - size = mServer->mChunkedMessage.size(); - - // Ignore the handshake message that occurs after startup. - } else if (size < 8) { - return true; - } - - mServer->mChunkedMessageRemaining = 0; - - // Every WebSocket message is prefixed with a command name followed by a space. - // - // For now we simply use istringstream for parsing, so command arguments are delimited - // with space characters. - // - // The "API index" matches the values of filament::backend::Backend (zero is invalid). - // - // The "shader index" is a zero-based index into the list of variants using the order that - // they appear in the package, where each API (GL / VK / Metal) has its own list. - // - // Commands: - // - // EDIT [material id] [api index] [shader index] [shader length] [shader source....] - // - - const static std::string_view kEditCmd = "EDIT "; - const static size_t kEditCmdLength = kEditCmd.size(); - - if (0 == strncmp(data, kEditCmd.data(), kEditCmdLength)) { - std::string command(data + kEditCmdLength, size - kEditCmdLength); - std::istringstream str(command); - uint32_t matid; - int api; - int shaderIndex; - int shaderLength; - str >> std::hex >> matid >> std::dec >> api >> shaderIndex >> shaderLength; - const char* source = data + kEditCmdLength + str.tellg() + 1; - const size_t remaining = size - kEditCmdLength - str.tellg(); - - // Return early and wait for more chunks if some bytes are still outstanding. - if (remaining < shaderLength + 1) { - mServer->mChunkedMessage = CString(data, size); - mServer->mChunkedMessageRemaining = shaderLength + 1 - remaining; - return true; - } - - mServer->handleEditCommand(matid, backend::Backend(api), shaderIndex, source, - shaderLength); - return true; - } - - const std::string firstFewChars(data, std::min(size, size_t(8))); - slog.e << "Bad WebSocket message. First few characters: " - << "[" << firstFewChars << "]" << io::endl; - return false; - } - - void handleClose(CivetServer *server, const struct mg_connection *conn) override { - struct mg_connection *key = const_cast(conn); - mConnections.erase(key); - } - - // Notify all JavaScript clients that a new material package has been loaded. - void addMaterial(const DebugServer::MaterialRecord& material) { - for (auto connection : mConnections) { - char matid[9] = {}; - snprintf(matid, sizeof(matid), "%8.8x", material.key); - mg_websocket_write(connection, MG_WEBSOCKET_OPCODE_TEXT, matid, 8); - } - } - -private: - DebugServer* mServer; - tsl::robin_set mConnections; -}; +#endif DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) { #if !SERVE_FROM_SOURCE_TREE @@ -512,12 +176,10 @@ DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) { } mFileHandler = new FileRequestHandler(this); - mRestHandler = new RestRequestHandler(this); - mWebSocketHandler = new WebSocketHandler(this); + mApiHandler = new ApiHandler(this); - mServer->addHandler("/api", mRestHandler); + mServer->addHandler("/api", mApiHandler); mServer->addHandler("", mFileHandler); - mServer->addWebSocketHandler("", mWebSocketHandler); slog.i << "DebugServer listening at http://localhost:" << port << io::endl; filamat::GLSLTools::init(); @@ -525,12 +187,17 @@ DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) { DebugServer::~DebugServer() { filamat::GLSLTools::shutdown(); + + mServer->close(); + + delete mFileHandler; + delete mApiHandler; + delete mServer; + + std::unique_lock lock(mMaterialRecordsMutex); for (auto& pair : mMaterialRecords) { delete [] pair.second.package; } - delete mFileHandler; - delete mRestHandler; - delete mServer; } MaterialKey @@ -549,23 +216,27 @@ DebugServer::addMaterial(const CString& name, const void* data, size_t size, voi uint8_t* package = new uint8_t[size]; memcpy(package, data, size); + std::unique_lock lock(mMaterialRecordsMutex); MaterialRecord info = {userdata, package, size, name, key}; mMaterialRecords.insert({key, info}); - mWebSocketHandler->addMaterial(info); + mApiHandler->addMaterial(&info); return key; } void DebugServer::removeMaterial(MaterialKey key) { + std::unique_lock lock(mMaterialRecordsMutex); mMaterialRecords.erase(key); } -const DebugServer::MaterialRecord* DebugServer::getRecord(const MaterialKey& key) const { +const MaterialRecord* DebugServer::getRecord(const MaterialKey& key) const { + std::unique_lock lock(mMaterialRecordsMutex); const auto& iter = mMaterialRecords.find(key); return iter == mMaterialRecords.end() ? nullptr : &iter->second; } void DebugServer::updateActiveVariants() { if (mQueryCallback) { + std::unique_lock lock(mMaterialRecordsMutex); auto curr = mMaterialRecords.begin(); auto end = mMaterialRecords.end(); while (curr != end) { @@ -584,6 +255,7 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api return false; }; + std::unique_lock lock(mMaterialRecordsMutex); if (mMaterialRecords.find(key) == mMaterialRecords.end()) { return error(__LINE__); } @@ -600,7 +272,7 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api shaderCount = getShaderCount(package, ChunkType::MaterialGlsl); infos.reserve(shaderCount); infos.resize(shaderCount); - if (!getGlShaderInfo(package, infos.data())) { + if (!getShaderInfo(package, infos.data(), ChunkType::MaterialGlsl)) { return error(__LINE__); } break; @@ -609,7 +281,7 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api shaderCount = getShaderCount(package, ChunkType::MaterialSpirv); infos.reserve(shaderCount); infos.resize(shaderCount); - if (!getVkShaderInfo(package, infos.data())) { + if (!getShaderInfo(package, infos.data(), ChunkType::MaterialSpirv)) { return error(__LINE__); } break; @@ -618,7 +290,7 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api shaderCount = getShaderCount(package, ChunkType::MaterialMetal); infos.reserve(shaderCount); infos.resize(shaderCount); - if (!getMetalShaderInfo(package, infos.data())) { + if (!getShaderInfo(package, infos.data(), ChunkType::MaterialMetal)) { return error(__LINE__); } break; @@ -649,5 +321,4 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api return true; } -} // namespace matdbg -} // namespace filament +} // namespace filament::matdbg diff --git a/libs/matdbg/src/JsonWriter.cpp b/libs/matdbg/src/JsonWriter.cpp index ba052252c21..c9e94c8143d 100644 --- a/libs/matdbg/src/JsonWriter.cpp +++ b/libs/matdbg/src/JsonWriter.cpp @@ -139,7 +139,7 @@ static void printShaderInfo(ostream& json, const vector& info, const static bool printGlslInfo(ostream& json, const ChunkContainer& container) { std::vector info; info.resize(getShaderCount(container, ChunkType::MaterialGlsl)); - if (!getGlShaderInfo(container, info.data())) { + if (!getShaderInfo(container, info.data(), ChunkType::MaterialGlsl)) { return false; } json << "\"opengl\": [\n"; @@ -151,7 +151,7 @@ static bool printGlslInfo(ostream& json, const ChunkContainer& container) { static bool printVkInfo(ostream& json, const ChunkContainer& container) { std::vector info; info.resize(getShaderCount(container, ChunkType::MaterialSpirv)); - if (!getVkShaderInfo(container, info.data())) { + if (!getShaderInfo(container, info.data(), ChunkType::MaterialSpirv)) { return false; } json << "\"vulkan\": [\n"; @@ -163,7 +163,7 @@ static bool printVkInfo(ostream& json, const ChunkContainer& container) { static bool printMetalInfo(ostream& json, const ChunkContainer& container) { std::vector info; info.resize(getShaderCount(container, ChunkType::MaterialMetal)); - if (!getMetalShaderInfo(container, info.data())) { + if (!getShaderInfo(container, info.data(), ChunkType::MaterialMetal)) { return false; } json << "\"metal\": [\n"; @@ -227,17 +227,17 @@ bool JsonWriter::writeActiveInfo(const filaflat::ChunkContainer& package, switch (backend) { case Backend::OPENGL: shaders.resize(getShaderCount(package, ChunkType::MaterialGlsl)); - getGlShaderInfo(package, shaders.data()); + getShaderInfo(package, shaders.data(), ChunkType::MaterialGlsl); json << "opengl"; break; case Backend::VULKAN: shaders.resize(getShaderCount(package, ChunkType::MaterialSpirv)); - getVkShaderInfo(package, shaders.data()); + getShaderInfo(package, shaders.data(), ChunkType::MaterialSpirv); json << "vulkan"; break; case Backend::METAL: shaders.resize(getShaderCount(package, ChunkType::MaterialMetal)); - getMetalShaderInfo(package, shaders.data()); + getShaderInfo(package, shaders.data(), ChunkType::MaterialMetal); json << "metal"; break; default: diff --git a/libs/matdbg/src/ShaderExtractor.cpp b/libs/matdbg/src/ShaderExtractor.cpp index 3f10b60a014..f9dbe0ea1eb 100644 --- a/libs/matdbg/src/ShaderExtractor.cpp +++ b/libs/matdbg/src/ShaderExtractor.cpp @@ -38,23 +38,25 @@ using namespace utils; namespace filament { namespace matdbg { -ShaderExtractor::ShaderExtractor(Backend backend, const void* data, size_t size) +ShaderExtractor::ShaderExtractor(backend::ShaderLanguage target, const void* data, size_t size) : mChunkContainer(data, size), mMaterialChunk(mChunkContainer) { - switch (backend) { - case Backend::OPENGL: + switch (target) { + case backend::ShaderLanguage::ESSL1: + mMaterialTag = ChunkType::MaterialEssl1; + mDictionaryTag = ChunkType::DictionaryText; + break; + case backend::ShaderLanguage::ESSL3: mMaterialTag = ChunkType::MaterialGlsl; mDictionaryTag = ChunkType::DictionaryText; break; - case Backend::METAL: + case backend::ShaderLanguage::MSL: mMaterialTag = ChunkType::MaterialMetal; mDictionaryTag = ChunkType::DictionaryText; break; - case Backend::VULKAN: + case backend::ShaderLanguage::SPIRV: mMaterialTag = ChunkType::MaterialSpirv; mDictionaryTag = ChunkType::DictionarySpirv; break; - default: - break; } } diff --git a/libs/matdbg/src/ShaderInfo.cpp b/libs/matdbg/src/ShaderInfo.cpp index 6b857a5b9dd..cba7bf648df 100644 --- a/libs/matdbg/src/ShaderInfo.cpp +++ b/libs/matdbg/src/ShaderInfo.cpp @@ -48,61 +48,16 @@ size_t getShaderCount(const ChunkContainer& container, ChunkType type) { return shaderCount; } -bool getMetalShaderInfo(const ChunkContainer& container, ShaderInfo* info) { - if (!container.hasChunk(ChunkType::MaterialMetal)) { - return true; - } - - auto [start, end] = container.getChunkRange(ChunkType::MaterialMetal); - Unflattener unflattener(start, end); - - uint64_t shaderCount = 0; - if (!unflattener.read(&shaderCount) || shaderCount == 0) { - return false; - } - - for (uint64_t i = 0; i < shaderCount; i++) { - uint8_t shaderModelValue; - Variant variant; - uint8_t pipelineStageValue; - uint32_t offsetValue; - if (!unflattener.read(&shaderModelValue)) { - return false; - } - - if (!unflattener.read(&variant)) { - return false; - } - - if (!unflattener.read(&pipelineStageValue)) { - return false; - } - - if (!unflattener.read(&offsetValue)) { - return false; - } - - *info++ = { - .shaderModel = ShaderModel(shaderModelValue), - .variant = variant, - .pipelineStage = ShaderStage(pipelineStageValue), - .offset = offsetValue - }; - } - - return true; -} - -bool getGlShaderInfo(const ChunkContainer& container, ShaderInfo* info) { - if (!container.hasChunk(ChunkType::MaterialGlsl)) { +bool getShaderInfo(const ChunkContainer& container, ShaderInfo* info, ChunkType chunkType) { + if (!container.hasChunk(chunkType)) { return true; } - auto [start, end] = container.getChunkRange(ChunkType::MaterialGlsl); + auto [start, end] = container.getChunkRange(chunkType); Unflattener unflattener(start, end); - uint64_t shaderCount; + uint64_t shaderCount = 0; if (!unflattener.read(&shaderCount) || shaderCount == 0) { return false; } @@ -139,49 +94,4 @@ bool getGlShaderInfo(const ChunkContainer& container, ShaderInfo* info) { return true; } -bool getVkShaderInfo(const ChunkContainer& container, ShaderInfo* info) { - if (!container.hasChunk(ChunkType::MaterialSpirv)) { - return true; - } - - auto [start, end] = container.getChunkRange(ChunkType::MaterialSpirv); - Unflattener unflattener(start, end); - - uint64_t shaderCount; - if (!unflattener.read(&shaderCount) || shaderCount == 0) { - return false; - } - - for (uint64_t i = 0; i < shaderCount; i++) { - uint8_t shaderModelValue; - Variant variant; - uint8_t pipelineStageValue; - uint32_t dictionaryIndex; - - if (!unflattener.read(&shaderModelValue)) { - return false; - } - - if (!unflattener.read(&variant)) { - return false; - } - - if (!unflattener.read(&pipelineStageValue)) { - return false; - } - - if (!unflattener.read(&dictionaryIndex)) { - return false; - } - - *info++ = { - .shaderModel = ShaderModel(shaderModelValue), - .variant = variant, - .pipelineStage = ShaderStage(pipelineStageValue), - .offset = dictionaryIndex - }; - } - return true; -} - } // namespace filament diff --git a/libs/matdbg/src/TextWriter.cpp b/libs/matdbg/src/TextWriter.cpp index d71a0933479..b74596f6a42 100644 --- a/libs/matdbg/src/TextWriter.cpp +++ b/libs/matdbg/src/TextWriter.cpp @@ -402,35 +402,29 @@ static void printShaderInfo(ostream& text, const vector& info, text << endl; } -static bool printGlslInfo(ostream& text, const ChunkContainer& container) { +static bool printShaderInfo(ostream& text, const ChunkContainer& container, ChunkType chunkType) { vector info; - info.resize(getShaderCount(container, ChunkType::MaterialGlsl)); - if (!getGlShaderInfo(container, info.data())) { + info.resize(getShaderCount(container, chunkType)); + if (!getShaderInfo(container, info.data(), chunkType)) { return false; } - text << "GLSL shaders:" << endl; - printShaderInfo(text, info, container); - return true; -} - -static bool printVkInfo(ostream& text, const ChunkContainer& container) { - vector info; - info.resize(getShaderCount(container, ChunkType::MaterialSpirv)); - if (!getVkShaderInfo(container, info.data())) { - return false; + switch (chunkType) { + case ChunkType::MaterialGlsl: + text << "GLSL shaders:" << endl; + break; + case ChunkType::MaterialEssl1: + text << "ESSL1 shaders:" << endl; + break; + case ChunkType::MaterialSpirv: + text << "Vulkan shaders:" << endl; + break; + case ChunkType::MaterialMetal: + text << "Metal shaders:" << endl; + break; + default: + assert(false && "Invalid shader ChunkType"); + break; } - text << "Vulkan shaders:" << endl; - printShaderInfo(text, info, container); - return true; -} - -static bool printMetalInfo(ostream& text, const ChunkContainer& container) { - vector info; - info.resize(getShaderCount(container, ChunkType::MaterialMetal)); - if (!getMetalShaderInfo(container, info.data())) { - return false; - } - text << "Metal shaders:" << endl; printShaderInfo(text, info, container); return true; } @@ -449,13 +443,16 @@ bool TextWriter::writeMaterialInfo(const filaflat::ChunkContainer& container) { if (!printSubpassesInfo(text, container)) { return false; } - if (!printGlslInfo(text, container)) { + if (!printShaderInfo(text, container, ChunkType::MaterialGlsl)) { + return false; + } + if (!printShaderInfo(text, container, ChunkType::MaterialEssl1)) { return false; } - if (!printVkInfo(text, container)) { + if (!printShaderInfo(text, container, ChunkType::MaterialSpirv)) { return false; } - if (!printMetalInfo(text, container)) { + if (!printShaderInfo(text, container, ChunkType::MaterialMetal)) { return false; } diff --git a/libs/matdbg/web/experiment/api.js b/libs/matdbg/web/experiment/api.js new file mode 100644 index 00000000000..edeb448f303 --- /dev/null +++ b/libs/matdbg/web/experiment/api.js @@ -0,0 +1,129 @@ +/* +* 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) + } +} diff --git a/libs/matdbg/web/experiment/app.js b/libs/matdbg/web/experiment/app.js new file mode 100644 index 00000000000..cf9d518c149 --- /dev/null +++ b/libs/matdbg/web/experiment/app.js @@ -0,0 +1,924 @@ +/* +* 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 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; +const MENU_HR = ` + display: block; + height: 1px; + border: 0px; + border-top: 1px solid ${UNSELECTED_COLOR}; + padding: 0; + width: 100%; + margin: 3px 0 8px 0; +`; +const MENU_SECTION_TITLE = ` + font-size: 16px; + color: ${UNSELECTED_COLOR}; +`; + +// 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');` + )}`; + } +}; + +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 + }); + 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)); + }); + } + + _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() { + this.dispatchEvent(new CustomEvent( + 'rebuild-shader', + {detail: this.editor.getValue(), bubbles: true, composed: true} + )); + } + + updated(props) { + if (props.has('code') && this.code.length > 0) { + this.editor.setValue(this.code); + } + if ((props.has('expectedWidth') || props.has('expectedHeight')) && + (this.expectedWidth > 0 && (this.expectedHeight - CODE_VIEWER_BOTTOM_ROW_HEIGHT) > 0)) { + this._editorDiv.style.width = Math.floor(this.expectedWidth) + 'px'; + this._editorDiv.style.height = + (Math.floor(this.expectedHeight) - CODE_VIEWER_BOTTOM_ROW_HEIGHT) + '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 MaterialInfo extends LitElement { + static get properties() { + return { + info: {type: Object, state: true}, + showing: {type: Boolean, state: true}, + } + } + + static get styles() { + return css` + :host { + font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px; + color: ${unsafeCSS(UNSELECTED_COLOR)}; + margin-bottom: 20px; + } + .section-title { + ${unsafeCSS(MENU_SECTION_TITLE)} + cursor: pointer; + } + hr { + ${unsafeCSS(MENU_HR)} + } + .hide { + display: none; + flex-direction: column; + } + .expander { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + } + `; + } + + constructor() { + super(); + this.showing = true; + this.info = null; + } + + _hasInfo() { + return this.info && Object.keys(this.info).length > 0; + } + + _showClick() { + this.showing = !this.showing; + } + + render() { + const expandedIcon = this._hasInfo() ? (this.showing ? '-' : '+') : ''; + 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]} +
+ `) + ); + } + } + let divClass = 'container'; + if (infoDivs.length == 0) { + divClass += ' hide'; + } + return html` +
+
+ Material Details ${expandedIcon} +
+
+ ${this.showing ? infoDivs : []} +
+ `; + } +} +customElements.define('material-info', MaterialInfo); + +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; + } + .material-section { + ${MENU_SECTION_TITLE} + } + .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; + } + hr { + ${MENU_HR} + } + `; + } + + 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'); + } + + 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.activeShaders && this.currentMaterial) { + const material = this.database[this.currentMaterial]; + const activeVariants = 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; + } + } + } + + _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} + `; + }); + return html` +
+
+ ${title} +
+
+ ${mats} +
+ `; + }; + return html` + +
+
matdbg
+ ${sections("Surface", "surface")} + ${sections("Post-processing", "postpro")} + +
+ `; + } + +} +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; + } + } + ); + + 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 (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]; + } + } + + 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; + } + })(); + } + } + + render() { + const shader = this._getShader(); + return html` + + + + + `; + } +} +customElements.define("matdbg-viewer", MatdbgViewer); diff --git a/libs/matdbg/web/experiment/index.html b/libs/matdbg/web/experiment/index.html new file mode 100644 index 00000000000..d49468873bf --- /dev/null +++ b/libs/matdbg/web/experiment/index.html @@ -0,0 +1,23 @@ + + + + Filament Debugger + + + + + + + + + + + + + diff --git a/libs/matdbg/web/script.js b/libs/matdbg/web/script.js index 5928858108a..5a7df5b557e 100644 --- a/libs/matdbg/web/script.js +++ b/libs/matdbg/web/script.js @@ -25,14 +25,14 @@ 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 gSocket = null; let gEditor = null; let gCurrentMaterial = "00000000"; let gCurrentLanguage = "glsl"; let gCurrentShader = { matid: "00000000", glindex: 0 }; -let gCurrentSocketId = 0; let gEditorIsLoading = false; require.config({ paths: { "vs": `${kMonacoBaseUrl}vs` }}); @@ -79,8 +79,9 @@ function rebuildMaterial() { } const editedText = shader[gCurrentLanguage]; - const byteCount = new Blob([editedText]).size; - gSocket.send(`EDIT ${gCurrentShader.matid} ${api} ${index} ${byteCount} ${editedText}`); + const req = new XMLHttpRequest(); + req.open('POST', '/api/edit'); + req.send(`${gCurrentShader.matid} ${api} ${index} ${editedText}`); } document.querySelector("body").addEventListener("click", (evt) => { @@ -165,16 +166,7 @@ function fetchMaterial(matid) { } function queryActiveShaders() { - if (!gSocket) { - 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(); + if (!isConnected()) { return; } fetch("api/active").then(function(response) { @@ -203,38 +195,54 @@ function queryActiveShaders() { }); } -function startSocket() { - const url = new URL(document.URL) - const ws = new WebSocket(`ws://${url.host}`); - - // When a new server has come online, ask it what materials it has. - ws.addEventListener("open", () => { - footer.innerText = `connection ${gCurrentSocketId}`; - gCurrentSocketId++; - - fetch("api/matids").then(function(response) { - return response.json(); - }).then(function(matInfo) { - for (matid of matInfo) { - if (!(matid in gMaterialDatabase)) { - fetchMaterial(matid); - } - } - }); - }); +function isConnected() { + return footer.innerText == 'connected'; +} - ws.addEventListener("close", (e) => { - footer.innerText = "no connection"; - gSocket = null; - setTimeout(() => startSocket(), 3000); +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); + } + } }); +} - ws.addEventListener("message", event => { - const matid = event.data; - 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(); +} - gSocket = ws; +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 if (matid != '1') { + fetchMaterial(matid); + } // else matid == '1' and it's a no-op, we just loop again. + statusLoop(); + }) + .catch(err => { + onDisconnected(); + setTimeout(statusLoop, STATUS_LOOP_TIMEOUT) + }); } function fetchMaterials() { @@ -497,7 +505,7 @@ function init() { Mustache.parse(matDetailTemplate.innerHTML); Mustache.parse(matListTemplate.innerHTML); - startSocket(); + statusLoop(); // Poll for active shaders once every second. // Take care not to poll more frequently than the frame rate. Active variants are determined diff --git a/libs/math/include/math/TQuatHelpers.h b/libs/math/include/math/TQuatHelpers.h index cd3342c0751..0439b9712b4 100644 --- a/libs/math/include/math/TQuatHelpers.h +++ b/libs/math/include/math/TQuatHelpers.h @@ -58,7 +58,8 @@ class TQuatProductOperators { /* compound assignment products by a scalar */ - constexpr QUATERNION& operator*=(T v) { + template>> + constexpr QUATERNION& operator*=(U v) { QUATERNION& lhs = static_cast&>(*this); for (size_t i = 0; i < QUATERNION::size(); i++) { lhs[i] *= v; @@ -66,7 +67,8 @@ class TQuatProductOperators { return lhs; } - constexpr QUATERNION& operator/=(T v) { + template>> + constexpr QUATERNION& operator/=(U v) { QUATERNION& lhs = static_cast&>(*this); for (size_t i = 0; i < QUATERNION::size(); i++) { lhs[i] /= v; @@ -85,28 +87,29 @@ class TQuatProductOperators { /* The operators below handle operation between quaternions of the same size * but of a different element type. */ - template - friend inline - constexpr QUATERNION MATH_PURE operator*(const QUATERNION& q, const QUATERNION& r) { + template + friend inline constexpr + QUATERNION> MATH_PURE operator*( + const QUATERNION& q, const QUATERNION& r) { // could be written as: // return QUATERNION( // q.w*r.w - dot(q.xyz, r.xyz), // q.w*r.xyz + r.w*q.xyz + cross(q.xyz, r.xyz)); - - return QUATERNION( + return { q.w * r.w - q.x * r.x - q.y * r.y - q.z * r.z, q.w * r.x + q.x * r.w + q.y * r.z - q.z * r.y, q.w * r.y - q.x * r.z + q.y * r.w + q.z * r.x, - q.w * r.z + q.x * r.y - q.y * r.x + q.z * r.w); + q.w * r.z + q.x * r.y - q.y * r.x + q.z * r.w + }; } - template - friend inline - constexpr TVec3 MATH_PURE operator*(const QUATERNION& q, const TVec3& v) { + template + friend inline constexpr + TVec3> MATH_PURE operator*(const QUATERNION& q, const TVec3& v) { // note: if q is known to be a unit quaternion, then this simplifies to: // TVec3 t = 2 * cross(q.xyz, v) // return v + (q.w * t) + cross(q.xyz, t) - return imaginary(q * QUATERNION(v, 0) * inverse(q)); + return imaginary(q * QUATERNION(v, 0) * inverse(q)); } @@ -122,22 +125,25 @@ class TQuatProductOperators { * q.w*r.z + q.x*r.y - q.y*r.x + q.z*r.w); * */ - friend inline - constexpr QUATERNION MATH_PURE operator*(QUATERNION q, T scalar) { + 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); } - friend inline - constexpr QUATERNION MATH_PURE operator*(T scalar, QUATERNION q) { + template>> + friend inline constexpr + 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); } - friend inline - constexpr QUATERNION MATH_PURE operator/(QUATERNION q, T scalar) { + 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); } }; @@ -160,9 +166,10 @@ class TQuatFunctions { * (the first one, BASE being known). */ - template - friend inline - constexpr T MATH_PURE dot(const QUATERNION& p, const QUATERNION& q) { + template + friend inline constexpr + arithmetic_result_t MATH_PURE dot( + const QUATERNION& p, const QUATERNION& q) { return p.x * q.x + p.y * q.y + p.z * q.z + @@ -196,7 +203,7 @@ class TQuatFunctions { friend inline constexpr QUATERNION MATH_PURE inverse(const QUATERNION& q) { - return conj(q) * (1 / dot(q, q)); + return conj(q) * (T(1) / dot(q, q)); } friend inline @@ -214,8 +221,10 @@ class TQuatFunctions { return QUATERNION(q.xyz, 0); } - friend inline - constexpr QUATERNION MATH_PURE cross(const QUATERNION& p, const QUATERNION& q) { + template + friend inline constexpr + QUATERNION> MATH_PURE cross( + const QUATERNION& p, const QUATERNION& q) { return unreal(p * q); } 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/utils/include/utils/EntityManager.h b/libs/utils/include/utils/EntityManager.h index 9674cac2554..8fac30cc532 100644 --- a/libs/utils/include/utils/EntityManager.h +++ b/libs/utils/include/utils/EntityManager.h @@ -44,23 +44,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 +70,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 +96,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/NameComponentManager.h b/libs/utils/include/utils/NameComponentManager.h index 9e31e4618de..a161b56ad74 100644 --- a/libs/utils/include/utils/NameComponentManager.h +++ b/libs/utils/include/utils/NameComponentManager.h @@ -48,7 +48,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 +75,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 +96,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/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/StructureOfArrays.h b/libs/utils/include/utils/StructureOfArrays.h index b6ea3bfbe47..497a0fefbd2 100644 --- a/libs/utils/include/utils/StructureOfArrays.h +++ b/libs/utils/include/utils/StructureOfArrays.h @@ -352,33 +352,55 @@ class StructureOfArraysBase { return push_back_unsafe(std::forward(args)...); } - // in C++20 we could use a lambda with explicit template parameter instead - struct PushBackUnsafeClosure { - size_t last; - std::tuple args; - inline explicit PushBackUnsafeClosure(size_t last, Structure&& args) - : last(last), args(std::forward(args)) {} - template - inline void operator()(TypeAt* p) { - new(p + last) TypeAt{ std::get(args) }; - } - }; + template + struct ElementIndices {}; + + template + struct BuildElementIndices : BuildElementIndices {}; + + template + struct BuildElementIndices<0, Indices...> : ElementIndices {}; + + template + void push_back_unsafe(Structure&& args, ElementIndices){ + size_t last = mSize++; + // Fold expression on the comma operator + ([&]{ + new(std::get(mArrays) + last) Elements{std::get(args)}; + }() , ...); + } + + template + void push_back_unsafe(Elements const& ... args, ElementIndices){ + size_t last = mSize++; + // Fold expression on the comma operator + ([&]{ + new(std::get(mArrays) + last) Elements{args}; + }() , ...); + } + + template + void push_back_unsafe(Elements && ... args, ElementIndices){ + size_t last = mSize++; + // Fold expression on the comma operator + ([&]{ + new(std::get(mArrays) + last) Elements{std::forward(args)}; + }() , ...); + } StructureOfArraysBase& push_back_unsafe(Structure&& args) noexcept { - for_each_index(mArrays, - PushBackUnsafeClosure{ mSize++, std::forward(args) }); + push_back_unsafe(std::forward(args), BuildElementIndices{}); return *this; } StructureOfArraysBase& push_back_unsafe(Elements const& ... args) noexcept { - for_each_index(mArrays, - PushBackUnsafeClosure{ mSize++, { args... } }); + push_back_unsafe(args..., BuildElementIndices{}); + return *this; } StructureOfArraysBase& push_back_unsafe(Elements&& ... args) noexcept { - for_each_index(mArrays, - PushBackUnsafeClosure{ mSize++, { std::forward(args)... }}); + push_back_unsafe(std::forward(args)..., BuildElementIndices{}); return *this; } 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/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/test/test_StructureOfArrays.cpp b/libs/utils/test/test_StructureOfArrays.cpp index 0b435e7656f..652d9d37fe1 100644 --- a/libs/utils/test/test_StructureOfArrays.cpp +++ b/libs/utils/test/test_StructureOfArrays.cpp @@ -173,3 +173,13 @@ TEST(StructureOfArraysTest, Simple) { soa.push_back(0.0f, 1.0, std::move(destroyedFloat4)); } +TEST(StructureOfArraysTest, MoveOnly) { + StructureOfArrays> soa; + soa.setCapacity(2); + soa.push_back(1.0f, std::make_unique(1)); + soa.push_back(2.0f, std::make_unique(2)); + EXPECT_EQ(soa.size(), 2); + EXPECT_EQ(*soa.elementAt<1>(0).get(), 1); + EXPECT_EQ(*soa.elementAt<1>(1).get(), 2); +} + diff --git a/libs/viewer/include/viewer/Settings.h b/libs/viewer/include/viewer/Settings.h index eeb62e2a784..fc46d2887ed 100644 --- a/libs/viewer/include/viewer/Settings.h +++ b/libs/viewer/include/viewer/Settings.h @@ -57,8 +57,9 @@ enum class ToneMapping : uint8_t { ACES_LEGACY = 1, ACES = 2, FILMIC = 3, - GENERIC = 4, - DISPLAY_RANGE = 5, + AGX = 4, + GENERIC = 5, + DISPLAY_RANGE = 6, }; using AmbientOcclusionOptions = filament::View::AmbientOcclusionOptions; @@ -114,8 +115,14 @@ struct GenericToneMapperSettings { float midGrayIn = 0.18f; float midGrayOut = 0.215f; float hdrMax = 10.0f; - bool operator!=(const GenericToneMapperSettings &rhs) const { return !(rhs == *this); } - bool operator==(const GenericToneMapperSettings &rhs) const; + bool operator!=(const GenericToneMapperSettings& rhs) const { return !(rhs == *this); } + bool operator==(const GenericToneMapperSettings& rhs) const; +}; + +struct AgxToneMapperSettings { + AgxToneMapper::AgxLook look = AgxToneMapper::AgxLook::NONE; + bool operator!=(const AgxToneMapperSettings& rhs) const { return !(rhs == *this); } + bool operator==(const AgxToneMapperSettings& rhs) const; }; struct ColorGradingSettings { @@ -127,7 +134,7 @@ struct ColorGradingSettings { filament::ColorGrading::QualityLevel quality = filament::ColorGrading::QualityLevel::MEDIUM; ToneMapping toneMapping = ToneMapping::ACES_LEGACY; bool padding0{}; - bool padding1{}; + AgxToneMapperSettings agxToneMapper; color::ColorSpace colorspace = Rec709-sRGB-D65; GenericToneMapperSettings genericToneMapper; math::float4 shadows{1.0f, 1.0f, 1.0f, 0.0f}; diff --git a/libs/viewer/include/viewer/ViewerGui.h b/libs/viewer/include/viewer/ViewerGui.h index 0130e28c766..6d6926a9aab 100644 --- a/libs/viewer/include/viewer/ViewerGui.h +++ b/libs/viewer/include/viewer/ViewerGui.h @@ -227,7 +227,7 @@ class UTILS_PUBLIC ViewerGui { */ Settings& getSettings() { return mSettings; } - void stopAnimation() { mCurrentAnimation = 0; } + void stopAnimation() { mCurrentAnimation = -1; } int getCurrentCamera() const { return mCurrentCamera; } @@ -256,7 +256,7 @@ class UTILS_PUBLIC ViewerGui { std::function mCustomUI; // Properties that can be changed from the UI. - int mCurrentAnimation = 1; // It is a 1-based index and 0 means not playing animation + int mCurrentAnimation = 0; // -1 means not playing animation and count means plays all of them (0-based index) int mCurrentVariant = 0; bool mEnableWireframe = false; int mVsmMsaaSamplesLog2 = 1; @@ -276,7 +276,7 @@ class UTILS_PUBLIC ViewerGui { // Cross fade animation parameters. float mCrossFadeDuration = 0.5f; // number of seconds to transition between animations - int mPreviousAnimation = 0; // one-based index of the previous animation + int mPreviousAnimation = -1; // zero-based index of the previous animation double mCurrentStartTime = 0.0f; // start time of most recent cross-fade (seconds) double mPreviousStartTime = 0.0f; // start time of previous cross-fade (seconds) bool mResetAnimation = true; // set when building ImGui widgets, honored in applyAnimation diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index f416fdd43a9..febeb321fd5 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -86,6 +86,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ToneMapp else if (0 == compare(tokens[i], jsonChunk, "ACES_LEGACY")) { *out = ToneMapping::ACES_LEGACY; } else if (0 == compare(tokens[i], jsonChunk, "ACES")) { *out = ToneMapping::ACES; } 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, "DISPLAY_RANGE")) { *out = ToneMapping::DISPLAY_RANGE; } else { @@ -130,6 +131,36 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, GenericT return i; } +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AgxToneMapper::AgxLook* out) { + if (0 == compare(tokens[i], jsonChunk, "NONE")) { *out = AgxToneMapper::AgxLook::NONE; } + else if (0 == compare(tokens[i], jsonChunk, "PUNCHY")) { *out = AgxToneMapper::AgxLook::PUNCHY; } + else if (0 == compare(tokens[i], jsonChunk, "GOLDEN")) { *out = AgxToneMapper::AgxLook::GOLDEN; } + else { + slog.w << "Invalid AgxLook: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AgxToneMapperSettings* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "look") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->look); + } else { + slog.w << "Invalid AgX tone mapper key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid AgX tone mapper value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ColorGradingSettings* out) { CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i++].size; @@ -146,6 +177,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ColorGra i = parse(tokens, i + 1, jsonChunk, &out->toneMapping); } else if (compare(tok, jsonChunk, "genericToneMapper") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->genericToneMapper); + } else if (compare(tok, jsonChunk, "agxToneMapper") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->agxToneMapper); } else if (compare(tok, jsonChunk, "luminanceScaling") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->luminanceScaling); } else if (compare(tok, jsonChunk, "gamutMapping") == 0) { @@ -579,8 +612,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); } @@ -619,6 +654,7 @@ constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noe case ToneMapping::ACES_LEGACY: return new ACESLegacyToneMapper; case ToneMapping::ACES: return new ACESToneMapper; 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, @@ -673,6 +709,7 @@ static std::ostream& operator<<(std::ostream& out, ToneMapping in) { case ToneMapping::ACES_LEGACY: return out << "\"ACES_LEGACY\""; case ToneMapping::ACES: return out << "\"ACES\""; case ToneMapping::FILMIC: return out << "\"FILMIC\""; + case ToneMapping::AGX: return out << "\"AGX\""; case ToneMapping::GENERIC: return out << "\"GENERIC\""; case ToneMapping::DISPLAY_RANGE: return out << "\"DISPLAY_RANGE\""; } @@ -688,6 +725,21 @@ static std::ostream& operator<<(std::ostream& out, const GenericToneMapperSettin << "}"; } +static std::ostream& operator<<(std::ostream& out, AgxToneMapper::AgxLook in) { + switch (in) { + case AgxToneMapper::AgxLook::NONE: return out << "\"NONE\""; + case AgxToneMapper::AgxLook::PUNCHY: return out << "\"PUNCHY\""; + case AgxToneMapper::AgxLook::GOLDEN: return out << "\"GOLDEN\""; + } + return out << "\"INVALID\""; +} + +static std::ostream& operator<<(std::ostream& out, const AgxToneMapperSettings& in) { + return out << "{\n" + << "\"look\": " << (in.look) << ",\n" + << "}"; +} + static std::ostream& operator<<(std::ostream& out, const ColorGradingSettings& in) { return out << "{\n" << "\"enabled\": " << to_string(in.enabled) << ",\n" @@ -695,6 +747,7 @@ static std::ostream& operator<<(std::ostream& out, const ColorGradingSettings& i << "\"quality\": " << (in.quality) << ",\n" << "\"toneMapping\": " << (in.toneMapping) << ",\n" << "\"genericToneMapper\": " << (in.genericToneMapper) << ",\n" + << "\"agxToneMapper\": " << (in.agxToneMapper) << ",\n" << "\"luminanceScaling\": " << to_string(in.luminanceScaling) << ",\n" << "\"gamutMapping\": " << to_string(in.gamutMapping) << ",\n" << "\"exposure\": " << (in.exposure) << ",\n" @@ -854,7 +907,7 @@ static std::ostream& operator<<(std::ostream& out, const Settings& in) { << "}"; } -bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings &rhs) const { +bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings& rhs) const { static_assert(sizeof(GenericToneMapperSettings) == 16, "Please update Settings.cpp"); return contrast == rhs.contrast && midGrayIn == rhs.midGrayIn && @@ -862,7 +915,12 @@ bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings &rhs) hdrMax == rhs.hdrMax; } -bool ColorGradingSettings::operator==(const ColorGradingSettings &rhs) const { +bool AgxToneMapperSettings::operator==(const AgxToneMapperSettings& rhs) const { + static_assert(sizeof(AgxToneMapperSettings) == 1, "Please update Settings.cpp"); + return look == rhs.look; +} + +bool ColorGradingSettings::operator==(const ColorGradingSettings& rhs) const { // If you had to fix the following codeline, then you likely also need to update the // implementation of operator==. static_assert(sizeof(ColorGradingSettings) == 312, "Please update Settings.cpp"); @@ -871,6 +929,7 @@ bool ColorGradingSettings::operator==(const ColorGradingSettings &rhs) const { quality == rhs.quality && toneMapping == rhs.toneMapping && genericToneMapper == rhs.genericToneMapper && + agxToneMapper == rhs.agxToneMapper && luminanceScaling == rhs.luminanceScaling && gamutMapping == rhs.gamutMapping && exposure == rhs.exposure && diff --git a/libs/viewer/src/Settings_generated.cpp b/libs/viewer/src/Settings_generated.cpp index 23449108b39..3a08774926a 100644 --- a/libs/viewer/src/Settings_generated.cpp +++ b/libs/viewer/src/Settings_generated.cpp @@ -782,6 +782,7 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ShadowType* out else if (0 == compare(tokens[i], jsonChunk, "VSM")) { *out = ShadowType::VSM; } else if (0 == compare(tokens[i], jsonChunk, "DPCF")) { *out = ShadowType::DPCF; } else if (0 == compare(tokens[i], jsonChunk, "PCSS")) { *out = ShadowType::PCSS; } + else if (0 == compare(tokens[i], jsonChunk, "PCFd")) { *out = ShadowType::PCFd; } else { slog.w << "Invalid ShadowType: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; } @@ -794,6 +795,7 @@ std::ostream& operator<<(std::ostream& out, ShadowType in) { case ShadowType::VSM: return out << "\"VSM\""; case ShadowType::DPCF: return out << "\"DPCF\""; case ShadowType::PCSS: return out << "\"PCSS\""; + case ShadowType::PCFd: return out << "\"PCFd\""; } return out << "\"INVALID\""; } diff --git a/libs/viewer/src/ViewerGui.cpp b/libs/viewer/src/ViewerGui.cpp index 4a94c4afac8..37d6420491c 100644 --- a/libs/viewer/src/ViewerGui.cpp +++ b/libs/viewer/src/ViewerGui.cpp @@ -133,6 +133,9 @@ static void computeToneMapPlot(ColorGradingSettings& settings, float* plot) { case ToneMapping::FILMIC: mapper = new FilmicToneMapper; break; + case ToneMapping::AGX: + mapper = new AgxToneMapper(settings.agxToneMapper.look); + break; case ToneMapping::GENERIC: mapper = new GenericToneMapper( settings.genericToneMapper.contrast, @@ -193,7 +196,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\0Generic\0Display Range\0\0"); + "Linear\0ACES (legacy)\0ACES\0Filmic\0AgX\0Generic\0Display Range\0\0"); colorGrading.toneMapping = (decltype(colorGrading.toneMapping)) toneMapping; if (colorGrading.toneMapping == ToneMapping::GENERIC) { if (ImGui::CollapsingHeader("Tonemap parameters")) { @@ -204,6 +207,11 @@ static void colorGradingUI(Settings& settings, float* rangePlot, float* curvePlo ImGui::SliderFloat("HDR max", &generic.hdrMax, 1.0f, 64.0f); } } + if (colorGrading.toneMapping == ToneMapping::AGX) { + int agxLook = (int) colorGrading.agxToneMapper.look; + ImGui::Combo("AgX Look", &agxLook, "None\0Punchy\0Golden\0\0"); + colorGrading.agxToneMapper.look = (decltype(colorGrading.agxToneMapper.look)) agxLook; + } computeToneMapPlot(colorGrading, toneMapPlot); @@ -516,19 +524,25 @@ void ViewerGui::applyAnimation(double currentTime, FilamentInstance* instance) { return; } Animator& animator = *instance->getAnimator(); - const size_t numAnimations = animator.getAnimationCount(); + const size_t animationCount = animator.getAnimationCount(); if (mResetAnimation) { mPreviousStartTime = mCurrentStartTime; mCurrentStartTime = currentTime; mResetAnimation = false; } const double elapsedSeconds = currentTime - mCurrentStartTime; - if (numAnimations > 0 && mCurrentAnimation > 0) { - animator.applyAnimation(mCurrentAnimation - 1, elapsedSeconds); - if (elapsedSeconds < mCrossFadeDuration && mPreviousAnimation > 0) { + if (animationCount > 0 && mCurrentAnimation >= 0) { + if (mCurrentAnimation == animationCount) { + for (size_t i = 0; i < animationCount; i++) { + animator.applyAnimation(i, elapsedSeconds); + } + } else { + animator.applyAnimation(mCurrentAnimation, elapsedSeconds); + } + if (elapsedSeconds < mCrossFadeDuration && mPreviousAnimation >= 0 && mPreviousAnimation != animationCount) { const double previousSeconds = currentTime - mPreviousStartTime; const float lerpFactor = elapsedSeconds / mCrossFadeDuration; - animator.applyCrossFade(mPreviousAnimation - 1, previousSeconds, lerpFactor); + animator.applyCrossFade(mPreviousAnimation, previousSeconds, lerpFactor); } } if (mShowingRestPose) { @@ -873,7 +887,7 @@ void ViewerGui::updateUserInterface() { ImGui::Checkbox("Enable LiSPSM", &light.shadowOptions.lispsm); int shadowType = (int)mSettings.view.shadowType; - ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0PCSS\0\0"); + ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0PCSS\0PCFd\0\0"); mSettings.view.shadowType = (ShadowType)shadowType; if (mSettings.view.shadowType == ShadowType::VSM) { @@ -1031,7 +1045,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]); @@ -1046,8 +1060,8 @@ 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()); @@ -1069,8 +1083,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 @@ -1098,18 +1120,20 @@ void ViewerGui::updateUserInterface() { } Animator& animator = *mInstance->getAnimator(); - if (animator.getAnimationCount() > 0 && ImGui::CollapsingHeader("Animation")) { + const size_t animationCount = animator.getAnimationCount(); + if (animationCount > 0 && ImGui::CollapsingHeader("Animation")) { ImGui::Indent(); int selectedAnimation = mCurrentAnimation; - ImGui::RadioButton("Disable", &selectedAnimation, 0); + ImGui::RadioButton("Disable", &selectedAnimation, -1); + ImGui::RadioButton("Apply all animations", &selectedAnimation, animationCount); ImGui::SliderFloat("Cross fade", &mCrossFadeDuration, 0.0f, 2.0f, "%4.2f seconds", ImGuiSliderFlags_AlwaysClamp); - for (size_t i = 0, count = animator.getAnimationCount(); i < count; ++i) { + for (size_t i = 0; i < animationCount; ++i) { std::string label = animator.getAnimationName(i); if (label.empty()) { label = "Unnamed " + std::to_string(i); } - ImGui::RadioButton(label.c_str(), &selectedAnimation, i + 1); + ImGui::RadioButton(label.c_str(), &selectedAnimation, i); } if (selectedAnimation != mCurrentAnimation) { mPreviousAnimation = mCurrentAnimation; diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 2303e412c45..1fb250897e9 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -135,7 +135,7 @@ struct App { static const char* DEFAULT_IBL = "assets/ibl/lightroom_14b"; static void printUsage(char* name) { - std::string exec_name(Path(name).getName()); + std::string const exec_name(Path(name).getName()); std::string usage( "SHOWCASE renders the specified glTF file, or a built-in file if none is specified\n" "Usage:\n" @@ -222,7 +222,7 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { int opt; int option_index = 0; while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) { - std::string arg(optarg ? optarg : ""); + std::string const arg(optarg ? optarg : ""); switch (opt) { default: case 'h': @@ -326,7 +326,7 @@ static void createGroundPlane(Engine* engine, Scene* scene, App& app) { Aabb aabb = app.asset->getBoundingBox(); if (!app.actualSize) { - mat4f transform = fitIntoUnitCube(aabb, 4); + mat4f const transform = fitIntoUnitCube(aabb, 4); aabb = aabb.transform(transform); } @@ -339,7 +339,7 @@ static void createGroundPlane(Engine* engine, Scene* scene, App& app) { { planeExtent.x, 0, -planeExtent.z }, }; - short4 tbn = packSnorm16( + short4 const tbn = packSnorm16( mat3f::packTangentFrame( mat3f{ float3{ 1.0f, 0.0f, 0.0f }, @@ -372,7 +372,7 @@ static void createGroundPlane(Engine* engine, Scene* scene, App& app) { indexBuffer->setBuffer(*engine, IndexBuffer::BufferDescriptor( indices, indexBuffer->getIndexCount() * sizeof(uint32_t))); - Entity groundPlane = em.create(); + Entity const groundPlane = em.create(); RenderableManager::Builder(1) .boundingBox({ {}, { planeExtent.x, 1e-4f, planeExtent.z } @@ -491,10 +491,10 @@ int main(int argc, char** argv) { app.config.title = "Filament"; app.config.iblDirectory = FilamentApp::getRootAssetsPath() + DEFAULT_IBL; - int optionIndex = handleCommandLineArguments(argc, argv, &app); + int const optionIndex = handleCommandLineArguments(argc, argv, &app); utils::Path filename; - int num_args = argc - optionIndex; + int const num_args = argc - optionIndex; if (num_args >= 1) { filename = argv[optionIndex]; if (!filename.exists()) { @@ -503,7 +503,7 @@ int main(int argc, char** argv) { } if (filename.isDirectory()) { auto files = filename.listContents(); - for (auto file : files) { + for (const auto& file : files) { if (file.getExtension() == "gltf" || file.getExtension() == "glb") { filename = file; break; @@ -516,9 +516,9 @@ int main(int argc, char** argv) { } } - auto loadAsset = [&app](utils::Path filename) { + auto loadAsset = [&app](const utils::Path& filename) { // Peek at the file size to allow pre-allocation. - long contentSize = static_cast(getFileSize(filename.c_str())); + long const contentSize = static_cast(getFileSize(filename.c_str())); if (contentSize <= 0) { std::cerr << "Unable to open " << filename << std::endl; exit(1); @@ -544,9 +544,9 @@ int main(int argc, char** argv) { } }; - auto loadResources = [&app] (utils::Path filename) { + auto loadResources = [&app] (const utils::Path& filename) { // Load external textures and buffers. - std::string gltfPath = filename.getAbsolutePath(); + std::string const gltfPath = filename.getAbsolutePath(); ResourceConfiguration configuration = {}; configuration.engine = app.engine; configuration.gltfPath = gltfPath.c_str(); @@ -637,8 +637,8 @@ int main(int argc, char** argv) { app.viewer->stopAnimation(); } - if (app.settingsFile.size() > 0) { - bool success = loadSettings(app.settingsFile.c_str(), &app.viewer->getSettings()); + if (!app.settingsFile.empty()) { + bool const success = loadSettings(app.settingsFile.c_str(), &app.viewer->getSettings()); if (success) { std::cout << "Loaded settings from " << app.settingsFile << std::endl; } else { @@ -688,7 +688,7 @@ int main(int argc, char** argv) { ImGui::Spacing(); } - float progress = app.resourceLoader->asyncGetLoadProgress(); + float const progress = app.resourceLoader->asyncGetLoadProgress(); if (progress < 1.0) { ImGui::ProgressBar(progress); } else { @@ -735,7 +735,7 @@ int main(int argc, char** argv) { } if (ImGui::Button("Export view settings")) { - automation.exportSettings(app.viewer->getSettings(), "settings.json"); + AutomationEngine::exportSettings(app.viewer->getSettings(), "settings.json"); app.messageBoxText = automation.getStatusMessage(); ImGui::OpenPopup("MessageBox"); } @@ -757,12 +757,33 @@ int main(int argc, char** argv) { debug.getPropertyAddress("d.renderer.doFrameCapture"); *captureFrame = true; } - ImGui::Checkbox("Disable buffer padding", debug.getPropertyAddress("d.renderer.disable_buffer_padding")); - ImGui::Checkbox("Camera at origin", debug.getPropertyAddress("d.view.camera_at_origin")); + ImGui::Checkbox("Disable buffer padding", + debug.getPropertyAddress("d.renderer.disable_buffer_padding")); + ImGui::Checkbox("Camera at origin", + debug.getPropertyAddress("d.view.camera_at_origin")); ImGui::Checkbox("Far Origin", &app.originIsFarAway); ImGui::SliderFloat("Origin", &app.originDistance, 0, 10000000); - ImGui::Checkbox("Far uses shadow casters", debug.getPropertyAddress("d.shadowmap.far_uses_shadowcasters")); - ImGui::Checkbox("Focus shadow casters", debug.getPropertyAddress("d.shadowmap.focus_shadowcasters")); + ImGui::Checkbox("Far uses shadow casters", + debug.getPropertyAddress("d.shadowmap.far_uses_shadowcasters")); + ImGui::Checkbox("Focus shadow casters", + debug.getPropertyAddress("d.shadowmap.focus_shadowcasters")); + + bool debugDirectionalShadowmap; + if (debug.getProperty("d.shadowmap.debug_directional_shadowmap", + &debugDirectionalShadowmap)) { + ImGui::Checkbox("Debug DIR shadowmap", &debugDirectionalShadowmap); + debug.setProperty("d.shadowmap.debug_directional_shadowmap", + debugDirectionalShadowmap); + } + + 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, @@ -815,7 +836,7 @@ int main(int argc, char** argv) { view->setStencilBufferEnabled(visualizeOverdraw); } - if (ImGui::BeginPopupModal("MessageBox", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal("MessageBox", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("%s", app.messageBoxText.c_str()); if (ImGui::Button("OK", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); @@ -861,7 +882,7 @@ int main(int argc, char** argv) { AssetLoader::destroy(&app.assetLoader); }; - auto animate = [&app](Engine* engine, View* view, double now) { + auto animate = [&app](Engine*, View*, double now) { app.resourceLoader->asyncUpdateLoad(); // Optionally fit the model into a unit cube at the origin. @@ -873,7 +894,7 @@ int main(int argc, char** argv) { app.viewer->applyAnimation(now); }; - auto resize = [&app](Engine* engine, View* view) { + auto resize = [&app](Engine*, View* view) { Camera& camera = view->getCamera(); if (&camera == app.mainCamera) { // Don't adjust the aspect ratio of the main camera, this is done inside of @@ -881,11 +902,11 @@ int main(int argc, char** argv) { return; } const Viewport& vp = view->getViewport(); - double aspectRatio = (double) vp.width / vp.height; + double const aspectRatio = (double) vp.width / vp.height; camera.setScaling({1.0 / aspectRatio, 1.0 }); }; - auto gui = [&app](Engine* engine, View* view) { + auto gui = [&app](Engine*, View*) { app.viewer->updateUserInterface(); FilamentApp::get().setSidebarWidth(app.viewer->getSidebarWidth()); @@ -918,7 +939,7 @@ int main(int argc, char** argv) { // Override the aspect ratio in the glTF file and adjust the aspect ratio of this // camera to the viewport. const Viewport& vp = view->getViewport(); - double aspectRatio = (double) vp.width / vp.height; + double const aspectRatio = (double) vp.width / vp.height; camera->setScaling({1.0 / aspectRatio, 1.0}); } @@ -963,7 +984,7 @@ int main(int argc, char** argv) { tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? app.originDistance : 0.0f })); // Check if color grading has changed. - ColorGradingSettings& options = app.viewer->getSettings().view.colorGrading; + ColorGradingSettings const& options = app.viewer->getSettings().view.colorGrading; if (options.enabled) { if (options != app.lastColorGradingOptions) { ColorGrading *colorGrading = createColorGrading(options, engine); @@ -977,12 +998,12 @@ int main(int argc, char** argv) { } }; - auto postRender = [&app](Engine* engine, View* view, Scene* scene, Renderer* renderer) { + auto postRender = [&app](Engine* engine, View* view, Scene*, Renderer* renderer) { if (app.automationEngine->shouldClose()) { FilamentApp::get().close(); return; } - AutomationEngine::ViewerContent content = { + AutomationEngine::ViewerContent const content = { .view = view, .renderer = renderer, .materials = app.instance->getMaterialInstances(), diff --git a/shaders/src/fog.fs b/shaders/src/fog.fs index 98d5ae928c2..17029480ca1 100644 --- a/shaders/src/fog.fs +++ b/shaders/src/fog.fs @@ -48,7 +48,7 @@ vec4 fog(vec4 color, highp vec3 view) { // when sampling the IBL we need to take into account the IBL transform. We know it's a // a rigid transform, so we can take the transpose instead of the inverse, and for the - // same reason we can use it directly instead of taking the cof() to transfrom a vector. + // same reason we can use it directly instead of taking the cof() to transform a vector. highp mat3 worldFromUserWorldMatrix = transpose(mat3(frameUniforms.userWorldFromWorldMatrix)); fogColor *= textureLod(light_fog, worldFromUserWorldMatrix * view, lod).rgb; } 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 07cf5bd24a5..375960515ea 100644 --- a/shaders/src/main.fs +++ b/shaders/src/main.fs @@ -66,6 +66,25 @@ void main() { fragColor = fog(fragColor, view); #endif +#if defined(VARIANT_HAS_SHADOWING) && defined(VARIANT_HAS_DIRECTIONAL_LIGHTING) + if (CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP) { + float a = fragColor.a; + highp vec4 p = getShadowPosition(getShadowCascade()); + p.xy = p.xy * (1.0 / p.w); + if (p.xy != saturate(p.xy)) { + vec4 c = vec4(1.0, 0, 1.0, 1.0) * a; + fragColor = mix(fragColor, c, 0.2); + } else { + highp vec2 size = vec2(textureSize(light_shadowMap, 0)); + highp int ix = int(floor(p.x * size.x)); + highp int iy = int(floor(p.y * size.y)); + float t = float((ix ^ iy) & 1) * 0.2; + vec4 c = vec4(vec3(t * a), a); + fragColor = mix(fragColor, c, 0.5); + } + } +#endif + #if MATERIAL_FEATURE_LEVEL == 0 if (CONFIG_SRGB_SWAPCHAIN_EMULATION) { if (frameUniforms.rec709 != 0) { diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 2f3a6cc0bb0..9a3eb63302f 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -85,24 +85,8 @@ float ShadowSample_PCF(const mediump sampler2DArray map, const uint layer, const highp vec4 shadowPosition) { highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); // note: shadowPosition.z is in the [1, 0] range (reversed Z) - highp vec2 size = vec2(textureSize(map, 0)); highp vec2 tc = clamp(position.xy, scissorNormalized.xy, scissorNormalized.zw); - highp vec2 st = tc.xy * size - 0.5; - - vec4 d; -#if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) - d = textureGather(map, vec3(tc, layer), 0); // 01, 11, 10, 00 -#else - // we must use texelFetchOffset before texelLodOffset filters - d[0] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(0, 1)).r; - d[1] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(1, 1)).r; - d[2] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(1, 0)).r; - d[3] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(0, 0)).r; -#endif - - vec4 pcf = step(0.0, position.zzzz - d); - highp vec2 grad = fract(st); - return mix(mix(pcf.w, pcf.z, grad.x), mix(pcf.x, pcf.y, grad.x), grad.y); + return step(0.0, position.z - textureLod(map, vec3(tc, layer), 0.0).r); } //------------------------------------------------------------------------------ diff --git a/third_party/cgltf/cgltf.h b/third_party/cgltf/cgltf.h index a534cefb4f4..95b56ceda5f 100644 --- a/third_party/cgltf/cgltf.h +++ b/third_party/cgltf/cgltf.h @@ -80,9 +80,13 @@ * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t * and only works with single-component data types. * - * `cgltf_copy_extras_json` allows users to retrieve the "extras" data that can be attached to many - * glTF objects (which can be arbitrary JSON data). This is a legacy function, consider using - * cgltf_extras::data directly instead. You can parse this data using your own JSON parser + * `cgltf_result cgltf_copy_extras_json(const cgltf_data*, const cgltf_extras*, + * char* dest, cgltf_size* dest_size)` allows users to retrieve the "extras" data that + * can be attached to many glTF objects (which can be arbitrary JSON data). The + * `cgltf_extras` struct stores the offsets of the start and end of the extras JSON data + * as it appears in the complete glTF JSON data. This function copies the extras data + * into the provided buffer. If `dest` is NULL, the length of the data is written into + * `dest_size`. You can then parse this data using your own JSON parser * or, if you've included the cgltf implementation using the integrated JSMN JSON parser. */ #ifndef CGLTF_H_INCLUDED__ @@ -252,10 +256,8 @@ typedef enum cgltf_data_free_method { } cgltf_data_free_method; typedef struct cgltf_extras { - cgltf_size start_offset; /* this field is deprecated and will be removed in the future; use data instead */ - cgltf_size end_offset; /* this field is deprecated and will be removed in the future; use data instead */ - - char* data; + cgltf_size start_offset; + cgltf_size end_offset; } cgltf_extras; typedef struct cgltf_extension { @@ -430,6 +432,8 @@ typedef struct cgltf_pbr_metallic_roughness cgltf_float base_color_factor[4]; cgltf_float metallic_factor; cgltf_float roughness_factor; + + cgltf_extras extras; } cgltf_pbr_metallic_roughness; typedef struct cgltf_pbr_specular_glossiness @@ -837,7 +841,6 @@ cgltf_size cgltf_num_components(cgltf_type type); cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count); -/* this function is deprecated and will be removed in the future; use cgltf_extras::data instead */ cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size); #ifdef __cplusplus @@ -921,15 +924,12 @@ static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t */ -#ifndef CGLTF_CONSTS static const cgltf_size GlbHeaderSize = 12; static const cgltf_size GlbChunkHeaderSize = 8; static const uint32_t GlbVersion = 2; static const uint32_t GlbMagic = 0x46546C67; static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; static const uint32_t GlbMagicBinChunk = 0x004E4942; -#define CGLTF_CONSTS -#endif #ifndef CGLTF_MALLOC #define CGLTF_MALLOC(size) malloc(size) @@ -1745,12 +1745,7 @@ cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* return cgltf_result_success; } -static void cgltf_free_extras(cgltf_data* data, cgltf_extras* extras) -{ - data->memory.free_func(data->memory.user_data, extras->data); -} - -static void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count) +void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count) { for (cgltf_size i = 0; i < extensions_count; ++i) { @@ -1760,12 +1755,6 @@ static void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, data->memory.free_func(data->memory.user_data, extensions); } -static void cgltf_free_texture_view(cgltf_data* data, cgltf_texture_view* view) -{ - cgltf_free_extensions(data, view->extensions, view->extensions_count); - cgltf_free_extras(data, &view->extras); -} - void cgltf_free(cgltf_data* data) { if (!data) @@ -1781,7 +1770,6 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->asset.min_version); cgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count); - cgltf_free_extras(data, &data->asset.extras); for (cgltf_size i = 0; i < data->accessors_count; ++i) { @@ -1792,12 +1780,8 @@ void cgltf_free(cgltf_data* data) cgltf_free_extensions(data, data->accessors[i].sparse.extensions, data->accessors[i].sparse.extensions_count); cgltf_free_extensions(data, data->accessors[i].sparse.indices_extensions, data->accessors[i].sparse.indices_extensions_count); cgltf_free_extensions(data, data->accessors[i].sparse.values_extensions, data->accessors[i].sparse.values_extensions_count); - cgltf_free_extras(data, &data->accessors[i].sparse.extras); - cgltf_free_extras(data, &data->accessors[i].sparse.indices_extras); - cgltf_free_extras(data, &data->accessors[i].sparse.values_extras); } cgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count); - cgltf_free_extras(data, &data->accessors[i].extras); } data->memory.free_func(data->memory.user_data, data->accessors); @@ -1807,7 +1791,6 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->buffer_views[i].data); cgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count); - cgltf_free_extras(data, &data->buffer_views[i].extras); } data->memory.free_func(data->memory.user_data, data->buffer_views); @@ -1827,8 +1810,8 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->buffers[i].uri); cgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count); - cgltf_free_extras(data, &data->buffers[i].extras); } + data->memory.free_func(data->memory.user_data, data->buffers); for (cgltf_size i = 0; i < data->meshes_count; ++i) @@ -1866,15 +1849,9 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes); } - for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) - { - cgltf_free_extras(data, &data->meshes[i].primitives[j].mappings[k].extras); - } - data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].mappings); cgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count); - cgltf_free_extras(data, &data->meshes[i].primitives[j].extras); } data->memory.free_func(data->memory.user_data, data->meshes[i].primitives); @@ -1886,7 +1863,6 @@ void cgltf_free(cgltf_data* data) } cgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count); - cgltf_free_extras(data, &data->meshes[i].extras); data->memory.free_func(data->memory.user_data, data->meshes[i].target_names); } @@ -1899,50 +1875,49 @@ void cgltf_free(cgltf_data* data) if(data->materials[i].has_pbr_metallic_roughness) { - cgltf_free_texture_view(data, &data->materials[i].pbr_metallic_roughness.metallic_roughness_texture); - cgltf_free_texture_view(data, &data->materials[i].pbr_metallic_roughness.base_color_texture); + cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions_count); } if(data->materials[i].has_pbr_specular_glossiness) { - cgltf_free_texture_view(data, &data->materials[i].pbr_specular_glossiness.diffuse_texture); - cgltf_free_texture_view(data, &data->materials[i].pbr_specular_glossiness.specular_glossiness_texture); + cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions_count); } if(data->materials[i].has_clearcoat) { - cgltf_free_texture_view(data, &data->materials[i].clearcoat.clearcoat_texture); - cgltf_free_texture_view(data, &data->materials[i].clearcoat.clearcoat_roughness_texture); - cgltf_free_texture_view(data, &data->materials[i].clearcoat.clearcoat_normal_texture); + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_texture.extensions, data->materials[i].clearcoat.clearcoat_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_normal_texture.extensions, data->materials[i].clearcoat.clearcoat_normal_texture.extensions_count); } if(data->materials[i].has_specular) { - cgltf_free_texture_view(data, &data->materials[i].specular.specular_texture); - cgltf_free_texture_view(data, &data->materials[i].specular.specular_color_texture); + cgltf_free_extensions(data, data->materials[i].specular.specular_texture.extensions, data->materials[i].specular.specular_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].specular.specular_color_texture.extensions, data->materials[i].specular.specular_color_texture.extensions_count); } if(data->materials[i].has_transmission) { - cgltf_free_texture_view(data, &data->materials[i].transmission.transmission_texture); + cgltf_free_extensions(data, data->materials[i].transmission.transmission_texture.extensions, data->materials[i].transmission.transmission_texture.extensions_count); } if (data->materials[i].has_volume) { - cgltf_free_texture_view(data, &data->materials[i].volume.thickness_texture); + cgltf_free_extensions(data, data->materials[i].volume.thickness_texture.extensions, data->materials[i].volume.thickness_texture.extensions_count); } if(data->materials[i].has_sheen) { - cgltf_free_texture_view(data, &data->materials[i].sheen.sheen_color_texture); - cgltf_free_texture_view(data, &data->materials[i].sheen.sheen_roughness_texture); + cgltf_free_extensions(data, data->materials[i].sheen.sheen_color_texture.extensions, data->materials[i].sheen.sheen_color_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].sheen.sheen_roughness_texture.extensions, data->materials[i].sheen.sheen_roughness_texture.extensions_count); } if(data->materials[i].has_iridescence) { - cgltf_free_texture_view(data, &data->materials[i].iridescence.iridescence_texture); - cgltf_free_texture_view(data, &data->materials[i].iridescence.iridescence_thickness_texture); + cgltf_free_extensions(data, data->materials[i].iridescence.iridescence_texture.extensions, data->materials[i].iridescence.iridescence_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].iridescence.iridescence_thickness_texture.extensions, data->materials[i].iridescence.iridescence_thickness_texture.extensions_count); } - cgltf_free_texture_view(data, &data->materials[i].normal_texture); - cgltf_free_texture_view(data, &data->materials[i].occlusion_texture); - cgltf_free_texture_view(data, &data->materials[i].emissive_texture); + cgltf_free_extensions(data, data->materials[i].normal_texture.extensions, data->materials[i].normal_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].occlusion_texture.extensions, data->materials[i].occlusion_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].emissive_texture.extensions, data->materials[i].emissive_texture.extensions_count); cgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count); - cgltf_free_extras(data, &data->materials[i].extras); } data->memory.free_func(data->memory.user_data, data->materials); @@ -1954,7 +1929,6 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->images[i].mime_type); cgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count); - cgltf_free_extras(data, &data->images[i].extras); } data->memory.free_func(data->memory.user_data, data->images); @@ -1962,9 +1936,7 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->textures_count; ++i) { data->memory.free_func(data->memory.user_data, data->textures[i].name); - cgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count); - cgltf_free_extras(data, &data->textures[i].extras); } data->memory.free_func(data->memory.user_data, data->textures); @@ -1972,9 +1944,7 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->samplers_count; ++i) { data->memory.free_func(data->memory.user_data, data->samplers[i].name); - cgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count); - cgltf_free_extras(data, &data->samplers[i].extras); } data->memory.free_func(data->memory.user_data, data->samplers); @@ -1985,7 +1955,6 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->skins[i].joints); cgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count); - cgltf_free_extras(data, &data->skins[i].extras); } data->memory.free_func(data->memory.user_data, data->skins); @@ -1993,18 +1962,7 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->cameras_count; ++i) { data->memory.free_func(data->memory.user_data, data->cameras[i].name); - - if (data->cameras[i].type == cgltf_camera_type_perspective) - { - cgltf_free_extras(data, &data->cameras[i].data.perspective.extras); - } - else if (data->cameras[i].type == cgltf_camera_type_orthographic) - { - cgltf_free_extras(data, &data->cameras[i].data.orthographic.extras); - } - cgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count); - cgltf_free_extras(data, &data->cameras[i].extras); } data->memory.free_func(data->memory.user_data, data->cameras); @@ -2012,8 +1970,6 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->lights_count; ++i) { data->memory.free_func(data->memory.user_data, data->lights[i].name); - - cgltf_free_extras(data, &data->lights[i].extras); } data->memory.free_func(data->memory.user_data, data->lights); @@ -2023,19 +1979,7 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->nodes[i].name); data->memory.free_func(data->memory.user_data, data->nodes[i].children); data->memory.free_func(data->memory.user_data, data->nodes[i].weights); - - if (data->nodes[i].has_mesh_gpu_instancing) - { - for (cgltf_size j = 0; j < data->nodes[i].mesh_gpu_instancing.attributes_count; ++j) - { - data->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes[j].name); - } - - data->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes); - } - cgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count); - cgltf_free_extras(data, &data->nodes[i].extras); } data->memory.free_func(data->memory.user_data, data->nodes); @@ -2046,7 +1990,6 @@ void cgltf_free(cgltf_data* data) data->memory.free_func(data->memory.user_data, data->scenes[i].nodes); cgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count); - cgltf_free_extras(data, &data->scenes[i].extras); } data->memory.free_func(data->memory.user_data, data->scenes); @@ -2057,19 +2000,16 @@ void cgltf_free(cgltf_data* data) for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) { cgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count); - cgltf_free_extras(data, &data->animations[i].samplers[j].extras); } data->memory.free_func(data->memory.user_data, data->animations[i].samplers); for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) { cgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count); - cgltf_free_extras(data, &data->animations[i].channels[j].extras); } data->memory.free_func(data->memory.user_data, data->animations[i].channels); cgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count); - cgltf_free_extras(data, &data->animations[i].extras); } data->memory.free_func(data->memory.user_data, data->animations); @@ -2077,14 +2017,11 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->variants_count; ++i) { data->memory.free_func(data->memory.user_data, data->variants[i].name); - - cgltf_free_extras(data, &data->variants[i].extras); } data->memory.free_func(data->memory.user_data, data->variants); cgltf_free_extensions(data, data->data_extensions, data->data_extensions_count); - cgltf_free_extras(data, &data->extras); for (cgltf_size i = 0; i < data->extensions_used_count; ++i) { @@ -2746,27 +2683,11 @@ static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t con return i; } -static int cgltf_parse_json_extras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) +static int cgltf_parse_json_extras(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) { - if (out_extras->data) - { - return CGLTF_ERROR_JSON; - } - - /* fill deprecated fields for now, this will be removed in the future */ + (void)json_chunk; out_extras->start_offset = tokens[i].start; out_extras->end_offset = tokens[i].end; - - size_t start = tokens[i].start; - size_t size = tokens[i].end - start; - out_extras->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1); - if (!out_extras->data) - { - return CGLTF_ERROR_NOMEM; - } - strncpy(out_extras->data, (const char*)json_chunk + start, size); - out_extras->data[size] = '\0'; - i = cgltf_skip_json(tokens, i); return i; } @@ -2921,7 +2842,7 @@ static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmnto int material = -1; int variants_tok = -1; - int extras_tok = -1; + cgltf_extras extras = {0, 0}; for (int k = 0; k < obj_size; ++k) { @@ -2942,8 +2863,7 @@ static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmnto } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - extras_tok = i + 1; - i = cgltf_skip_json(tokens, extras_tok); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &extras); } else { @@ -2971,13 +2891,7 @@ static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmnto out_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material); out_mappings[*offset].variant = variant; - - if (extras_tok >= 0) - { - int e = cgltf_parse_json_extras(options, tokens, extras_tok, json_chunk, &out_mappings[*offset].extras); - if (e < 0) - return e; - } + out_mappings[*offset].extras = extras; (*offset)++; } @@ -3092,7 +3006,7 @@ static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* t } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_prim->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_prim->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -3339,7 +3253,7 @@ static int cgltf_parse_json_accessor_sparse(cgltf_options* options, jsmntok_t co } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sparse->indices_extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->indices_extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -3382,7 +3296,7 @@ static int cgltf_parse_json_accessor_sparse(cgltf_options* options, jsmntok_t co } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sparse->values_extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->values_extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -3401,7 +3315,7 @@ static int cgltf_parse_json_accessor_sparse(cgltf_options* options, jsmntok_t co } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sparse->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -3524,7 +3438,7 @@ static int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t const* to } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_accessor->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_accessor->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -3630,7 +3544,7 @@ static int cgltf_parse_json_texture_view(cgltf_options* options, jsmntok_t const } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_texture_view->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture_view->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -3726,6 +3640,10 @@ static int cgltf_parse_json_pbr_metallic_roughness(cgltf_options* options, jsmnt i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->metallic_roughness_texture); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_pbr->extras); + } else { i = cgltf_skip_json(tokens, i+1); @@ -4158,7 +4076,7 @@ static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* token } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_image->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_image->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -4227,7 +4145,7 @@ static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tok } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -4276,7 +4194,7 @@ static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tok } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_texture->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -4439,7 +4357,7 @@ static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* to } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_material->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_material->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -4792,7 +4710,7 @@ static int cgltf_parse_json_buffer_view(cgltf_options* options, jsmntok_t const* } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer_view->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer_view->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -4895,7 +4813,7 @@ static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* toke } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -4979,7 +4897,7 @@ static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_skin->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_skin->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -5033,6 +4951,19 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke { i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name); } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "perspective") == 0) + { + out_camera->type = cgltf_camera_type_perspective; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "orthographic") == 0) + { + out_camera->type = cgltf_camera_type_orthographic; + } + ++i; + } else if (cgltf_json_strcmp(tokens+i, json_chunk, "perspective") == 0) { ++i; @@ -5042,11 +4973,6 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke int data_size = tokens[i].size; ++i; - if (out_camera->type != cgltf_camera_type_invalid) - { - return CGLTF_ERROR_JSON; - } - out_camera->type = cgltf_camera_type_perspective; for (int k = 0; k < data_size; ++k) @@ -5081,7 +5007,7 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.perspective.extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.perspective.extras); } else { @@ -5103,11 +5029,6 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke int data_size = tokens[i].size; ++i; - if (out_camera->type != cgltf_camera_type_invalid) - { - return CGLTF_ERROR_JSON; - } - out_camera->type = cgltf_camera_type_orthographic; for (int k = 0; k < data_size; ++k) @@ -5140,7 +5061,7 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras); } else { @@ -5155,7 +5076,7 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -5288,7 +5209,7 @@ static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* token } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_light->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_light->extras); } else { @@ -5414,7 +5335,7 @@ static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_node->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_node->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -5552,7 +5473,7 @@ static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* token } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_scene->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_scene->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -5634,7 +5555,7 @@ static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -5714,7 +5635,7 @@ static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_channel->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_channel->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -5796,7 +5717,7 @@ static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* t } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_animation->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_animation->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -5852,7 +5773,7 @@ static int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t const* tok } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_variant->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_variant->extras); } else { @@ -5916,7 +5837,7 @@ static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* token } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_asset->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_asset->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { @@ -6072,7 +5993,7 @@ static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens } else if (cgltf_json_strcmp(tokens+i, json_chunk, "extras") == 0) { - i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_data->extras); + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_data->extras); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) { diff --git a/third_party/cgltf/cgltf_write.h b/third_party/cgltf/cgltf_write.h index 033b0d1ccab..5c4edaf72da 100644 --- a/third_party/cgltf/cgltf_write.h +++ b/third_party/cgltf/cgltf_write.h @@ -15,17 +15,19 @@ * * Reference: * `cgltf_result cgltf_write_file(const cgltf_options* options, const char* - * path, const cgltf_data* data)` writes a glTF data to the given file path. - * If `options->type` is `cgltf_file_type_glb`, both JSON content and binary - * buffer of the given glTF data will be written in a GLB format. - * Otherwise, only the JSON part will be written. - * External buffers and images are not written out. `data` is not deallocated. + * path, const cgltf_data* data)` writes JSON to the given file path. Buffer + * files and external images are not written out. `data` is not deallocated. * * `cgltf_size cgltf_write(const cgltf_options* options, char* buffer, * cgltf_size size, const cgltf_data* data)` writes JSON into the given memory * buffer. Returns the number of bytes written to `buffer`, including a null * terminator. If buffer is null, returns the number of bytes that would have * been written. `data` is not deallocated. + * + * To write custom JSON into the `extras` field, aggregate all the custom JSON + * into a single buffer, then set `file_data` to this buffer. By supplying + * start_offset and end_offset values for various objects, you can select a + * range of characters within the aggregated buffer. */ #ifndef CGLTF_WRITE_H_INCLUDED__ #define CGLTF_WRITE_H_INCLUDED__ @@ -145,17 +147,6 @@ typedef struct { context->needs_comma = 1; } #define CGLTF_WRITE_TEXTURE_INFO(label, info) if (info.texture) { \ - cgltf_write_line(context, "\"" label "\": {"); \ - CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ - cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ - if (info.has_transform) { \ - context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ - cgltf_write_texture_transform(context, &info.transform); \ - } \ - cgltf_write_extras(context, &info.extras); \ - cgltf_write_line(context, "}"); } - -#define CGLTF_WRITE_NORMAL_TEXTURE_INFO(label, info) if (info.texture) { \ cgltf_write_line(context, "\"" label "\": {"); \ CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ @@ -167,28 +158,6 @@ typedef struct { cgltf_write_extras(context, &info.extras); \ cgltf_write_line(context, "}"); } -#define CGLTF_WRITE_OCCLUSION_TEXTURE_INFO(label, info) if (info.texture) { \ - cgltf_write_line(context, "\"" label "\": {"); \ - CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ - cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ - cgltf_write_floatprop(context, "strength", info.scale, 1.0f); \ - if (info.has_transform) { \ - context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ - cgltf_write_texture_transform(context, &info.transform); \ - } \ - cgltf_write_extras(context, &info.extras); \ - cgltf_write_line(context, "}"); } - -#ifndef CGLTF_CONSTS -static const cgltf_size GlbHeaderSize = 12; -static const cgltf_size GlbChunkHeaderSize = 8; -static const uint32_t GlbVersion = 2; -static const uint32_t GlbMagic = 0x46546C67; -static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; -static const uint32_t GlbMagicBinChunk = 0x004E4942; -#define CGLTF_CONSTS -#endif - static void cgltf_write_indent(cgltf_write_context* context) { if (context->needs_comma) @@ -239,24 +208,15 @@ static void cgltf_write_strprop(cgltf_write_context* context, const char* label, static void cgltf_write_extras(cgltf_write_context* context, const cgltf_extras* extras) { - if (extras->data) + cgltf_size length = extras->end_offset - extras->start_offset; + if (length > 0 && context->data->file_data) { + char* json_string = ((char*) context->data->file_data) + extras->start_offset; cgltf_write_indent(context); - CGLTF_SPRINTF("\"extras\": %s", extras->data); + CGLTF_SPRINTF("%s", "\"extras\": "); + CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string); context->needs_comma = 1; } - else - { - cgltf_size length = extras->end_offset - extras->start_offset; - if (length > 0 && context->data->json) - { - char* json_string = ((char*) context->data->json) + extras->start_offset; - cgltf_write_indent(context); - CGLTF_SPRINTF("%s", "\"extras\": "); - CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string); - context->needs_comma = 1; - } - } } static void cgltf_write_stritem(cgltf_write_context* context, const char* item) @@ -653,6 +613,7 @@ static void cgltf_write_material(cgltf_write_context* context, const cgltf_mater { cgltf_write_floatarrayprop(context, "baseColorFactor", params->base_color_factor, 4); } + cgltf_write_extras(context, ¶ms->extras); cgltf_write_line(context, "}"); } @@ -665,7 +626,7 @@ static void cgltf_write_material(cgltf_write_context* context, const cgltf_mater cgltf_write_line(context, "\"KHR_materials_clearcoat\": {"); CGLTF_WRITE_TEXTURE_INFO("clearcoatTexture", params->clearcoat_texture); CGLTF_WRITE_TEXTURE_INFO("clearcoatRoughnessTexture", params->clearcoat_roughness_texture); - CGLTF_WRITE_NORMAL_TEXTURE_INFO("clearcoatNormalTexture", params->clearcoat_normal_texture); + CGLTF_WRITE_TEXTURE_INFO("clearcoatNormalTexture", params->clearcoat_normal_texture); cgltf_write_floatprop(context, "clearcoatFactor", params->clearcoat_factor, 0.0f); cgltf_write_floatprop(context, "clearcoatRoughnessFactor", params->clearcoat_roughness_factor, 0.0f); cgltf_write_line(context, "}"); @@ -770,8 +731,8 @@ static void cgltf_write_material(cgltf_write_context* context, const cgltf_mater cgltf_write_line(context, "}"); } - CGLTF_WRITE_NORMAL_TEXTURE_INFO("normalTexture", material->normal_texture); - CGLTF_WRITE_OCCLUSION_TEXTURE_INFO("occlusionTexture", material->occlusion_texture); + CGLTF_WRITE_TEXTURE_INFO("normalTexture", material->normal_texture); + CGLTF_WRITE_TEXTURE_INFO("occlusionTexture", material->occlusion_texture); CGLTF_WRITE_TEXTURE_INFO("emissiveTexture", material->emissive_texture); if (cgltf_check_floatarray(material->emissive_factor, 3, 0.0f)) { @@ -1136,47 +1097,6 @@ static void cgltf_write_variant(cgltf_write_context* context, const cgltf_materi cgltf_write_line(context, "}"); } -static void cgltf_write_glb(FILE* file, const void* json_buf, const cgltf_size json_size, const void* bin_buf, const cgltf_size bin_size) -{ - char header[GlbHeaderSize]; - char chunk_header[GlbChunkHeaderSize]; - char json_pad[3] = { 0x20, 0x20, 0x20 }; - char bin_pad[3] = { 0, 0, 0 }; - - cgltf_size json_padsize = (json_size % 4 != 0) ? 4 - json_size % 4 : 0; - cgltf_size bin_padsize = (bin_size % 4 != 0) ? 4 - bin_size % 4 : 0; - cgltf_size total_size = GlbHeaderSize + GlbChunkHeaderSize + json_size + json_padsize; - if (bin_buf != NULL && bin_size > 0) { - total_size += GlbChunkHeaderSize + bin_size + bin_padsize; - } - - // Write a GLB header - memcpy(header, &GlbMagic, 4); - memcpy(header + 4, &GlbVersion, 4); - memcpy(header + 8, &total_size, 4); - fwrite(header, 1, GlbHeaderSize, file); - - // Write a JSON chunk (header & data) - uint32_t json_chunk_size = (uint32_t)(json_size + json_padsize); - memcpy(chunk_header, &json_chunk_size, 4); - memcpy(chunk_header + 4, &GlbMagicJsonChunk, 4); - fwrite(chunk_header, 1, GlbChunkHeaderSize, file); - - fwrite(json_buf, 1, json_size, file); - fwrite(json_pad, 1, json_padsize, file); - - if (bin_buf != NULL && bin_size > 0) { - // Write a binary chunk (header & data) - uint32_t bin_chunk_size = (uint32_t)(bin_size + bin_padsize); - memcpy(chunk_header, &bin_chunk_size, 4); - memcpy(chunk_header + 4, &GlbMagicBinChunk, 4); - fwrite(chunk_header, 1, GlbChunkHeaderSize, file); - - fwrite(bin_buf, 1, bin_size, file); - fwrite(bin_pad, 1, bin_padsize, file); - } -} - cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data) { cgltf_size expected = cgltf_write(options, NULL, 0, data); @@ -1185,18 +1105,13 @@ cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, co if (expected != actual) { fprintf(stderr, "Error: expected %zu bytes but wrote %zu bytes.\n", expected, actual); } - FILE* file = fopen(path, "wb"); + FILE* file = fopen(path, "wt"); if (!file) { return cgltf_result_file_not_found; } // Note that cgltf_write() includes a null terminator, which we omit from the file content. - if (options->type == cgltf_file_type_glb) { - cgltf_write_glb(file, buffer, actual - 1, data->bin, data->bin_size); - } else { - // Write a plain JSON file. - fwrite(buffer, actual - 1, 1, file); - } + fwrite(buffer, actual - 1, 1, file); fclose(file); free(buffer); return cgltf_result_success; diff --git a/third_party/cgltf/test/CMakeLists.txt b/third_party/cgltf/test/CMakeLists.txt index d62d01d6ccd..aa31730524f 100644 --- a/third_party/cgltf/test/CMakeLists.txt +++ b/third_party/cgltf/test/CMakeLists.txt @@ -41,19 +41,6 @@ else() endif() install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) -set( EXE_NAME test_write_glb ) -add_executable( ${EXE_NAME} test_write_glb.cpp ) -set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) -if(MSVC) - target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) - add_definitions( -D_CRT_SECURE_NO_WARNINGS) -else() - target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) - target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) - target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) -endif() -install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) - set( EXE_NAME test_math ) add_executable( ${EXE_NAME} test_math.cpp ) set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) diff --git a/third_party/cgltf/test/test_all.py b/third_party/cgltf/test/test_all.py index ac0882f5d82..ec9b9f01641 100755 --- a/third_party/cgltf/test/test_all.py +++ b/third_party/cgltf/test/test_all.py @@ -59,7 +59,6 @@ def collect_files(path, type, name): collect_files("glTF-Sample-Models/2.0/", ".glb", "test_conversion") collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_conversion") collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_write") - collect_files("glTF-Sample-Models/2.0/", ".glb", "test_write_glb") result = os.system(get_executable_path("test_math")) if result != 0: diff --git a/third_party/cgltf/test/test_write_glb.cpp b/third_party/cgltf/test/test_write_glb.cpp deleted file mode 100644 index c3c5b4430e8..00000000000 --- a/third_party/cgltf/test/test_write_glb.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#define CGLTF_IMPLEMENTATION -#define CGLTF_WRITE_IMPLEMENTATION -#include "../cgltf_write.h" - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (argc < 2) - { - printf("err\n"); - return -1; - } - - cgltf_options options = {}; - cgltf_data* data0 = NULL; - cgltf_result result = cgltf_parse_file(&options, argv[1], &data0); - - // Silently skip over files that are unreadable since this is a writing test. - if (result != cgltf_result_success) - { - return cgltf_result_success; - } - - options.type = cgltf_file_type_glb; // Write back in a GLB format - result = cgltf_write_file(&options, "out.glb", data0); - if (result != cgltf_result_success) - { - return result; - } - - cgltf_data* data1 = NULL; - result = cgltf_parse_file(&options, "out.glb", &data1); - if (result != cgltf_result_success) - { - return result; - } - - if (data0->meshes_count != data1->meshes_count) { - return -1; - } - - // Compare binary buffers - if (data0->bin_size != data1->bin_size) { - return -1; - } - if (memcmp(data0->bin, data1->bin, data0->bin_size) != 0) { - return -1; - } - - cgltf_free(data1); - cgltf_free(data0); - return cgltf_result_success; -} diff --git a/third_party/cgltf/tnt/README b/third_party/cgltf/tnt/README index 1e992ce14c7..fbfc97e770e 100644 --- a/third_party/cgltf/tnt/README +++ b/third_party/cgltf/tnt/README @@ -1,10 +1,10 @@ This folder was last updated as follows: - export sha=280ab89 + export tag=v1.13 cd third_party - curl -L -O https://github.com/jkuhlmann/cgltf/archive/${sha}.zip - unzip ${sha}.zip + curl -L -O https://github.com/jkuhlmann/cgltf/archive/refs/tags/${tag}.zip + unzip ${tag}.zip mv cgltf-* cgltf_new rsync -r cgltf_new/ cgltf/ --delete --exclude tnt - rm -rf ${sha}.zip cgltf_new + rm -rf ${tag}.zip cgltf_new git add cgltf ; git status diff --git a/tools/matinfo/src/main.cpp b/tools/matinfo/src/main.cpp index 15a458335bc..d5920023721 100644 --- a/tools/matinfo/src/main.cpp +++ b/tools/matinfo/src/main.cpp @@ -47,9 +47,11 @@ using utils::Path; struct Config { bool printGLSL = false; + bool printESSL1 = false; bool printSPIRV = false; bool printMetal = false; bool printDictionaryGLSL = false; + bool printDictionaryESSL1 = false; bool printDictionarySPIRV = false; bool printDictionaryMetal = false; bool transpile = false; @@ -76,6 +78,8 @@ static void printUsage(const char* name) { " Print this message\n\n" " --print-glsl=[index], -g\n" " Print GLSL for the nth shader (0 is the first OpenGL shader)\n\n" + " --print-essl1=[index], -G\n" + " Print GLES SL version 1 shader for the nth shader (0 is the first ESSL shader)\n\n" " --print-spirv=[index], -s\n" " Validate and print disasm for the nth shader (0 is the first Vulkan shader)\n\n" " --print-metal=[index], -m\n" @@ -84,6 +88,8 @@ static void printUsage(const char* name) { " Print the nth Vulkan shader transpiled into GLSL\n\n" " --print-dic-glsl\n" " Print the GLSL dictionary\n\n" + " --print-dic-essl1\n" + " Print the ESSL1 dictionary\n\n" " --print-dic-metal\n" " Print the Metal dictionary\n\n" " --print-dic-vk\n" @@ -117,16 +123,18 @@ static void license() { } static int handleArguments(int argc, char* argv[], Config* config) { - static constexpr const char* OPTSTR = "hla:g:s:v:b:m:b:w:xyz"; + static constexpr const char* OPTSTR = "hla:g:G:s:v:b:m:b:w:Xxyz"; static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, { "license", no_argument, nullptr, 'l' }, { "analyze-spirv", required_argument, nullptr, 'a' }, { "print-glsl", required_argument, nullptr, 'g' }, + { "print-essl1", required_argument, nullptr, 'G' }, { "print-spirv", required_argument, nullptr, 's' }, { "print-vkglsl", required_argument, nullptr, 'v' }, { "print-metal", required_argument, nullptr, 'm' }, { "print-dic-glsl", no_argument, nullptr, 'x' }, + { "print-dic-essl1", no_argument, nullptr, 'X' }, { "print-dic-metal", no_argument, nullptr, 'y' }, { "print-dic-vk", no_argument, nullptr, 'z' }, { "dump-binary", required_argument, nullptr, 'b' }, @@ -151,6 +159,10 @@ static int handleArguments(int argc, char* argv[], Config* config) { config->printGLSL = true; config->shaderIndex = static_cast(std::stoi(arg)); break; + case 'G': + config->printESSL1 = true; + config->shaderIndex = static_cast(std::stoi(arg)); + break; case 's': config->printSPIRV = true; config->shaderIndex = static_cast(std::stoi(arg)); @@ -180,6 +192,9 @@ static int handleArguments(int argc, char* argv[], Config* config) { case 'x': config->printDictionaryGLSL = true; break; + case 'X': + config->printDictionaryESSL1 = true; + break; case 'y': config->printDictionaryMetal = true; break; @@ -409,18 +424,18 @@ static bool parseChunks(Config config, void* data, size_t size) { return true; } - if (config.printGLSL || config.printSPIRV || config.printMetal) { + if (config.printGLSL || config.printESSL1 || config.printSPIRV || config.printMetal) { filaflat::ShaderContent content; std::vector info; if (config.printGLSL) { - ShaderExtractor parser(Backend::OPENGL, data, size); + ShaderExtractor parser(filament::backend::ShaderLanguage::ESSL3, data, size); if (!parser.parse()) { return false; } info.resize(getShaderCount(container, filamat::ChunkType::MaterialGlsl)); - if (!getGlShaderInfo(container, info.data())) { + if (!getShaderInfo(container, info.data(), filamat::ChunkType::MaterialGlsl)) { std::cerr << "Failed to parse GLSL chunk." << std::endl; return false; } @@ -439,14 +454,40 @@ static bool parseChunks(Config config, void* data, size_t size) { return true; } + if (config.printESSL1) { + ShaderExtractor parser(filament::backend::ShaderLanguage::ESSL1, data, size); + if (!parser.parse()) { + return false; + } + + info.resize(getShaderCount(container, filamat::ChunkType::MaterialEssl1)); + if (!getShaderInfo(container, info.data(), filamat::ChunkType::MaterialEssl1)) { + std::cerr << "Failed to parse ESSL1 chunk." << std::endl; + return false; + } + + if (config.shaderIndex >= info.size()) { + std::cerr << "Shader index out of range." << std::endl; + return false; + } + + const auto& item = info[config.shaderIndex]; + parser.getShader(item.shaderModel, item.variant, item.pipelineStage, content); + + // Cast to char* to print as a string rather than hex value. + std::cout << (const char*) content.data(); + + return true; + } + if (config.printSPIRV) { - ShaderExtractor parser(Backend::VULKAN, data, size); + ShaderExtractor parser(filament::backend::ShaderLanguage::SPIRV, data, size); if (!parser.parse()) { return false; } info.resize(getShaderCount(container, filamat::ChunkType::MaterialSpirv)); - if (!getVkShaderInfo(container, info.data())) { + if (!getShaderInfo(container, info.data(), filamat::ChunkType::MaterialSpirv)) { std::cerr << "Failed to parse SPIRV chunk." << std::endl; return false; } @@ -476,13 +517,13 @@ static bool parseChunks(Config config, void* data, size_t size) { } if (config.printMetal) { - ShaderExtractor parser(Backend::METAL, data, size); + ShaderExtractor parser(filament::backend::ShaderLanguage::MSL, data, size); if (!parser.parse()) { return false; } info.resize(getShaderCount(container, filamat::ChunkType::MaterialMetal)); - if (!getMetalShaderInfo(container, info.data())) { + if (!getShaderInfo(container, info.data(), filamat::ChunkType::MaterialMetal)) { std::cerr << "Failed to parse Metal chunk." << std::endl; return false; } @@ -502,11 +543,12 @@ static bool parseChunks(Config config, void* data, size_t size) { TextWriter writer; - if (config.printDictionaryGLSL || config.printDictionarySPIRV || config.printDictionaryMetal) { + if (config.printDictionaryGLSL || config.printDictionaryESSL1 || config.printDictionarySPIRV || config.printDictionaryMetal) { ShaderExtractor parser( - (config.printDictionaryGLSL ? Backend::OPENGL : - (config.printDictionarySPIRV ? Backend::VULKAN : - (config.printDictionaryMetal ? Backend::METAL : Backend::DEFAULT))), data, size); + (config.printDictionaryGLSL ? filament::backend::ShaderLanguage::ESSL3 : + (config.printDictionaryESSL1 ? filament::backend::ShaderLanguage::ESSL1 : + (config.printDictionarySPIRV ? filament::backend::ShaderLanguage::SPIRV : + filament::backend::ShaderLanguage::MSL))), data, size); if (!parser.parse()) { return false; diff --git a/web/filament-js/README.md b/web/filament-js/README.md index f83fcab2c1b..bee62b75ef5 100644 --- a/web/filament-js/README.md +++ b/web/filament-js/README.md @@ -49,6 +49,4 @@ do `npm publish`. 9. Update the live drag-and-drop viewer as follows: 1. Edit the pinned Filament version in the `