Merge branch 'rc/1.45.1' into release
This commit is contained in:
+7
-5
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -154,6 +154,184 @@ public class Engine {
|
||||
FEATURE_LEVEL_2
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs <code>Engine</code> 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
|
||||
* <b>must be</b> 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 <code>Engine</code>, or <code>null</code> 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 @@ public class Engine {
|
||||
*/
|
||||
@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 class Engine {
|
||||
*/
|
||||
@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 class Engine {
|
||||
*/
|
||||
@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 @@ public class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class Engine {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -146,18 +146,29 @@ public class Scene {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of {@link RenderableManager} components in the <code>Scene</code>.
|
||||
* Returns the total number of Entities in the <code>Scene</code>, whether alive or not.
|
||||
*
|
||||
* @return number of {@link RenderableManager} components in the <code>Scene</code>..
|
||||
* @return the total number of Entities in the <code>Scene</code>.
|
||||
*/
|
||||
public int getEntityCount() {
|
||||
return nGetEntityCount(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of active (alive) {@link RenderableManager} components in the
|
||||
* <code>Scene</code>.
|
||||
*
|
||||
* @return number of {@link RenderableManager} components in the <code>Scene</code>.
|
||||
*/
|
||||
public int getRenderableCount() {
|
||||
return nGetRenderableCount(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of {@link LightManager} components in the <code>Scene</code>.
|
||||
* Returns the number of active (alive) {@link LightManager} components in the
|
||||
* <code>Scene</code>.
|
||||
*
|
||||
* @return number of {@link LightManager} components in the <code>Scene</code>..
|
||||
* @return number of {@link LightManager} components in the <code>Scene</code>.
|
||||
*/
|
||||
public int getLightCount() {
|
||||
return nGetLightCount(getNativeObject());
|
||||
@@ -189,6 +200,7 @@ public class Scene {
|
||||
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);
|
||||
|
||||
@@ -100,6 +100,45 @@ public class ToneMapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 class ToneMapper {
|
||||
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);
|
||||
|
||||
|
||||
@@ -1930,6 +1930,7 @@ public class View {
|
||||
* PCF with soft shadows and contact hardening
|
||||
*/
|
||||
PCSS,
|
||||
PCFd,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ filament-viewer::part(canvas) {
|
||||
</p>
|
||||
|
||||
</main>
|
||||
<script src="https://unpkg.com/filament@1.25.3/filament.js"></script>
|
||||
<script src="https://unpkg.com/filament@1.44.0/filament.js"></script>
|
||||
<script src="https://unpkg.com/gltumble"></script>
|
||||
<script src="filament-viewer.js" type="module"></script>
|
||||
</body>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -114,6 +114,7 @@ public:
|
||||
* Platform. The <insert> and <retrieve> 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 @@ public:
|
||||
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
|
||||
|
||||
@@ -42,6 +42,9 @@ public:
|
||||
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 @@ protected:
|
||||
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<EGLContext> mAdditionalContexts;
|
||||
@@ -146,8 +150,8 @@ protected:
|
||||
|
||||
void initializeGlExtensions() noexcept;
|
||||
|
||||
private:
|
||||
EGLConfig findSwapChainConfig(uint64_t flags) const;
|
||||
protected:
|
||||
EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -30,6 +30,9 @@ public:
|
||||
|
||||
Driver* createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept override;
|
||||
|
||||
protected:
|
||||
bool isOpenGL() const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
|
||||
@@ -89,36 +90,44 @@ public:
|
||||
|
||||
// ----------------------------------------------------
|
||||
// ---------- Platform Customization options ----------
|
||||
/**
|
||||
* The client preference can be stored within the struct. We allow for two specification of
|
||||
* preference:
|
||||
* 1) A substring to match against `VkPhysicalDeviceProperties.deviceName`.
|
||||
* 2) Index of the device in the list as returned by vkEnumeratePhysicalDevices.
|
||||
*/
|
||||
struct GPUPreference {
|
||||
std::string deviceName;
|
||||
int8_t index = -1;
|
||||
struct Customization {
|
||||
/**
|
||||
* The client can specify the GPU (i.e. VkDevice) for the platform. We allow the
|
||||
* following preferences:
|
||||
* 1) A substring to match against `VkPhysicalDeviceProperties.deviceName`. Empty string
|
||||
* by default.
|
||||
* 2) Index of the device in the list as returned by
|
||||
* `vkEnumeratePhysicalDevices`. -1 by default to indicate no preference.
|
||||
*/
|
||||
struct GPUPreference {
|
||||
utils::CString deviceName;
|
||||
int8_t index = -1;
|
||||
} gpu;
|
||||
|
||||
/**
|
||||
* Whether the platform supports sRGB swapchain. Default is true.
|
||||
*/
|
||||
bool isSRGBSwapChainSupported = true;
|
||||
|
||||
/**
|
||||
* When the platform window is resized, we will flush and wait on the command queues
|
||||
* before recreating the swapchain. Default is true.
|
||||
*/
|
||||
bool flushAndWaitOnWindowResize = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client can provide a preference over the GPU to use in the vulkan instance
|
||||
* @return `GPUPreference` struct that indicates the client's preference
|
||||
* Client can override to indicate customized behavior or parameter for their platform.
|
||||
* @return `Customization` struct that indicates the client's platform
|
||||
* customizations.
|
||||
*/
|
||||
virtual GPUPreference getPreferredGPU() noexcept {
|
||||
virtual Customization getCustomization() const noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
// -------- End platform customization options --------
|
||||
// ----------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns whether the platform supports sRGB swapchain. This is true by default, and the client
|
||||
* needs to override this method to specify otherwise.
|
||||
* @return Whether the platform supports sRGB swapchain.
|
||||
*/
|
||||
virtual bool isSRGBSwapChainSupported() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the images handles and format of the memory backing the swapchain. This should be called
|
||||
* after createSwapChain() or after recreateIfResized().
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<16, 64, 208>
|
||||
#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 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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<Allocator,
|
||||
utils::LockingPolicy::SpinLock,
|
||||
utils::LockingPolicy::Mutex,
|
||||
utils::TrackingPolicy::DebugAndHighWatermark>;
|
||||
#else
|
||||
using HandleArena = utils::Arena<Allocator,
|
||||
utils::LockingPolicy::SpinLock>;
|
||||
utils::LockingPolicy::Mutex>;
|
||||
#endif
|
||||
|
||||
// allocateHandle()/deallocateHandle() selects the pool to use at compile-time based on the
|
||||
@@ -256,6 +258,7 @@ private:
|
||||
HandleBase::HandleId allocateHandle() noexcept {
|
||||
if constexpr (SIZE <= P0) { return allocateHandleInPool<P0>(); }
|
||||
if constexpr (SIZE <= P1) { return allocateHandleInPool<P1>(); }
|
||||
static_assert(SIZE <= P2);
|
||||
return allocateHandleInPool<P2>();
|
||||
}
|
||||
|
||||
@@ -266,6 +269,7 @@ private:
|
||||
} else if constexpr (SIZE <= P1) {
|
||||
deallocateHandleFromPool<P1>(id);
|
||||
} else {
|
||||
static_assert(SIZE <= P2);
|
||||
deallocateHandleFromPool<P2>(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::hasBlobFunc() const noexcept {
|
||||
return mInsertBlob && mRetrieveBlob;
|
||||
bool Platform::hasInsertBlobFunc() const noexcept {
|
||||
return bool(mInsertBlob);
|
||||
}
|
||||
|
||||
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
||||
return bool(mRetrieveBlob);
|
||||
}
|
||||
|
||||
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define TNT_METALCONTEXT_H
|
||||
|
||||
#include "MetalResourceTracker.h"
|
||||
#include "MetalShaderCompiler.h"
|
||||
#include "MetalState.h"
|
||||
|
||||
#include <CoreVideo/CVMetalTextureCache.h>
|
||||
@@ -149,6 +150,8 @@ struct MetalContext {
|
||||
|
||||
MTLViewport currentViewport;
|
||||
|
||||
MetalShaderCompiler* shaderCompiler = nullptr;
|
||||
|
||||
#if defined(FILAMENT_METAL_PROFILING)
|
||||
// Logging and profiling.
|
||||
os_log_t log;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -144,6 +144,9 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
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 @@ MetalDriver::~MetalDriver() noexcept {
|
||||
delete mContext->bufferPool;
|
||||
delete mContext->blitter;
|
||||
delete mContext->timerQueryImpl;
|
||||
delete mContext->shaderCompiler;
|
||||
delete mContext;
|
||||
}
|
||||
|
||||
@@ -319,7 +323,7 @@ void MetalDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
}
|
||||
|
||||
void MetalDriver::createProgramR(Handle<HwProgram> rph, Program&& program) {
|
||||
construct_handle<MetalProgram>(rph, mContext->device, program);
|
||||
construct_handle<MetalProgram>(rph, *mContext, std::move(program));
|
||||
}
|
||||
|
||||
void MetalDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int dummy) {
|
||||
@@ -584,6 +588,7 @@ void MetalDriver::terminate() {
|
||||
|
||||
MetalExternalImage::shutdown(*mContext);
|
||||
mContext->blitter->shutdown();
|
||||
mContext->shaderCompiler->terminate();
|
||||
}
|
||||
|
||||
ShaderModel MetalDriver::getShaderModel() const noexcept {
|
||||
@@ -719,7 +724,7 @@ bool MetalDriver::isStereoSupported() {
|
||||
}
|
||||
|
||||
bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
@@ -957,7 +962,7 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
|
||||
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 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
auto program = handle_cast<MetalProgram>(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 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
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 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
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 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
|
||||
// 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 @@ void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGro
|
||||
|
||||
auto mtlProgram = handle_cast<MetalProgram>(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<MTLComputeCommandEncoder> computeEncoder =
|
||||
[getPendingCommandBuffer(mContext) computeCommandEncoder];
|
||||
|
||||
NSError* error = nil;
|
||||
id<MTLComputePipelineState> computePipelineState =
|
||||
[mContext->device newComputePipelineStateWithFunction:mtlProgram->computeFunction
|
||||
[mContext->device newComputePipelineStateWithFunction:compute
|
||||
error:&error];
|
||||
if (error) {
|
||||
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
@@ -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<Entry> bufferMapping;
|
||||
};
|
||||
|
||||
struct MetalProgram : public HwProgram {
|
||||
MetalProgram(id<MTLDevice> device, const Program& program) noexcept;
|
||||
class MetalProgram : public HwProgram {
|
||||
public:
|
||||
MetalProgram(MetalContext& context, Program&& program) noexcept;
|
||||
|
||||
id<MTLFunction> vertexFunction;
|
||||
id<MTLFunction> fragmentFunction;
|
||||
id<MTLFunction> computeFunction;
|
||||
const MetalShaderCompiler::MetalFunctionBundle& getFunctions();
|
||||
const Program::SamplerGroupInfo& getSamplerGroupInfo() { return samplerGroupInfo; }
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
|
||||
bool isValid = false;
|
||||
MetalContext& mContext;
|
||||
MetalShaderCompiler::MetalFunctionBundle mFunctionBundle;
|
||||
MetalShaderCompiler::program_token_t mToken;
|
||||
};
|
||||
|
||||
struct PixelBufferShape {
|
||||
|
||||
@@ -299,6 +299,9 @@ MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uin
|
||||
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
|
||||
|
||||
MetalRenderPrimitive::MetalRenderPrimitive()
|
||||
: bufferMapping(utils::FixedCapacityVector<Entry>::with_capacity(MAX_VERTEX_BUFFER_COUNT)) {}
|
||||
|
||||
void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer*
|
||||
indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
@@ -306,118 +309,94 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalInde
|
||||
|
||||
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<MTLDevice> device, const Program& program) noexcept
|
||||
: HwProgram(program.getName()), vertexFunction(nil), fragmentFunction(nil),
|
||||
computeFunction(nil), isValid(false) {
|
||||
|
||||
using MetalFunctionPtr = __strong id<MTLFunction>*;
|
||||
|
||||
static_assert(Program::SHADER_TYPE_COUNT == 3, "Only vertex, fragment, and/or compute shaders expected.");
|
||||
MetalFunctionPtr shaderFunctions[3] = { &vertexFunction, &fragmentFunction, &computeFunction };
|
||||
|
||||
const auto& sources = program.getShadersSource();
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const auto& source = sources[i];
|
||||
// It's okay for some shaders to be empty, they shouldn't be used in any draw calls.
|
||||
if (source.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_invariant( source[source.size() - 1] == '\0' );
|
||||
|
||||
// the shader string is null terminated and the length includes the null character
|
||||
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
|
||||
length:source.size() - 1
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
// When options is nil, Metal uses the most recent language version available.
|
||||
id<MTLLibrary> 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<MTLDataType, 3> 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<MTLFunction> function = [library newFunctionWithName:@"main0"
|
||||
constantValues:constants
|
||||
error:&error];
|
||||
if (!program.getName().empty()) {
|
||||
function.label = @(program.getName().c_str());
|
||||
}
|
||||
assert_invariant(function);
|
||||
*shaderFunctions[i] = function;
|
||||
}
|
||||
|
||||
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;
|
||||
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
|
||||
: HwProgram(program.getName()), mContext(context) {
|
||||
|
||||
// Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to
|
||||
// the appropriate stage(s).
|
||||
samplerGroupInfo = program.getSamplerGroupInfo();
|
||||
|
||||
mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program));
|
||||
assert_invariant(mToken);
|
||||
}
|
||||
|
||||
const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctions() {
|
||||
initialize();
|
||||
return mFunctionBundle;
|
||||
}
|
||||
|
||||
void MetalProgram::initialize() {
|
||||
if (!mToken) {
|
||||
return;
|
||||
}
|
||||
mFunctionBundle = mContext.shaderCompiler->getProgram(mToken);
|
||||
assert_invariant(!mToken);
|
||||
}
|
||||
|
||||
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels,
|
||||
|
||||
@@ -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 <backend/CallbackHandler.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class MetalDriver;
|
||||
|
||||
class MetalShaderCompiler {
|
||||
struct MetalProgramToken;
|
||||
|
||||
public:
|
||||
class MetalFunctionBundle {
|
||||
public:
|
||||
MetalFunctionBundle() = default;
|
||||
MetalFunctionBundle(id<MTLFunction> fragment, id<MTLFunction> vertex)
|
||||
: functions{fragment, vertex} {
|
||||
assert_invariant(fragment && vertex);
|
||||
assert_invariant(fragment.functionType == MTLFunctionTypeFragment);
|
||||
assert_invariant(vertex.functionType == MTLFunctionTypeVertex);
|
||||
}
|
||||
explicit MetalFunctionBundle(id<MTLFunction> compute) : functions{compute, nil} {
|
||||
assert_invariant(compute);
|
||||
assert_invariant(compute.functionType == MTLFunctionTypeKernel);
|
||||
}
|
||||
|
||||
std::pair<id<MTLFunction>, id<MTLFunction>> getRasterFunctions() const noexcept {
|
||||
assert_invariant(functions[0].functionType == MTLFunctionTypeFragment);
|
||||
assert_invariant(functions[1].functionType == MTLFunctionTypeVertex);
|
||||
return {functions[0], functions[1]};
|
||||
}
|
||||
|
||||
id<MTLFunction> 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<MTLFunction> functions[2] = {nil, nil};
|
||||
};
|
||||
|
||||
using program_token_t = std::shared_ptr<MetalProgramToken>;
|
||||
|
||||
explicit MetalShaderCompiler(id<MTLDevice> 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<MTLDevice> device);
|
||||
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
id<MTLDevice> mDevice;
|
||||
CallbackManager mCallbackManager;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_METAL_METALSHADERCOMPILER_H
|
||||
@@ -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 <backend/Program.h>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
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<MTLDevice> 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<MTLDevice> device) {
|
||||
std::array<id<MTLFunction>, 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<MTLLibrary> 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<MTLDataType, 3> 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<MTLFunction> 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<MTLFunction> vertexFunction = functions[0];
|
||||
id<MTLFunction> fragmentFunction = functions[1];
|
||||
id<MTLFunction> 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<MetalProgramToken>(*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
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include "OpenGLBlobCache.h"
|
||||
|
||||
#include "OpenGLContext.h"
|
||||
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
@@ -28,17 +30,18 @@ struct OpenGLBlobCache::Blob {
|
||||
char data[];
|
||||
};
|
||||
|
||||
GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
Program const& program) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
OpenGLBlobCache::OpenGLBlobCache(OpenGLContext& gl) noexcept
|
||||
: mCachingSupported(gl.gets.num_program_binary_formats >= 1) {
|
||||
}
|
||||
|
||||
if (!platform.hasBlobFunc()) {
|
||||
GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
Program const& program) const noexcept {
|
||||
SYSTRACE_CALL();
|
||||
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, decltype(&::free)> blob{ (Blob*)malloc(size), &::free };
|
||||
if (UTILS_LIKELY(blob)) {
|
||||
}
|
||||
if (programBinarySize) {
|
||||
size_t const size = sizeof(Blob) + programBinarySize;
|
||||
std::unique_ptr<Blob, decltype(&::free)> 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);
|
||||
}
|
||||
GLenum const error = glGetError();
|
||||
if (error == GL_NO_ERROR) {
|
||||
blob->format = format;
|
||||
platform.insertBlob(key.data(), key.size(), blob.get(), size);
|
||||
}
|
||||
}
|
||||
}
|
||||
#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, decltype(&::free)> blob{ (Blob*)malloc(size), &::free };
|
||||
if (UTILS_LIKELY(blob)) {
|
||||
blob->format = format;
|
||||
memcpy(blob->data, data, programBinarySize);
|
||||
platform.insertBlob(key.data(), key.size(), blob.get(), size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -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,
|
||||
GLuint retrieve(BlobCacheKey* key, Platform& platform,
|
||||
Program const& program) const noexcept;
|
||||
|
||||
void insert(Platform& platform,
|
||||
BlobCacheKey const& key, GLuint program) noexcept;
|
||||
|
||||
static void insert(Platform& platform, BlobCacheKey const& key,
|
||||
GLenum format, void* data, GLsizei programBinarySize) noexcept;
|
||||
|
||||
private:
|
||||
struct Blob;
|
||||
bool mCachingSupported = false;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -99,38 +99,41 @@ OpenGLContext::OpenGLContext() noexcept {
|
||||
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
|
||||
&gets.max_uniform_block_size);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
||||
&gets.max_uniform_buffer_bindings);
|
||||
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
|
||||
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);
|
||||
#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
|
||||
|
||||
@@ -153,14 +153,15 @@ public:
|
||||
// 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 = {};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include "CompilerThreadPool.h"
|
||||
#include "OpenGLBlobCache.h"
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/Program.h>
|
||||
@@ -95,6 +96,7 @@ public:
|
||||
|
||||
private:
|
||||
OpenGLDriver& mDriver;
|
||||
OpenGLBlobCache mBlobCache;
|
||||
CallbackManager mCallbackManager;
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
|
||||
|
||||
@@ -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<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||
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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||
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
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/bitset.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -100,8 +102,8 @@ public:
|
||||
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 @@ private:
|
||||
bool mDebugMarkersSupported = false;
|
||||
bool mDebugUtilsSupported = false;
|
||||
|
||||
VkFormat mDepthFormat;
|
||||
VkFormatList mDepthFormats;
|
||||
|
||||
// For convenience so that VulkanPlatform can initialize the private fields.
|
||||
friend class VulkanPlatform;
|
||||
|
||||
@@ -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<HwFence> 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<HwRenderPrimitive> 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<HwRenderPrimitive> 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<VulkanTexture*>(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;
|
||||
#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);
|
||||
}
|
||||
|
||||
samplerInfo[sampler.binding] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = imageView,
|
||||
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
|
||||
};
|
||||
samplerTextures[sampler.binding] = texture;
|
||||
}
|
||||
if (UTILS_UNLIKELY(!boundSampler->t)) {
|
||||
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
|
||||
continue;
|
||||
}
|
||||
|
||||
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(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;
|
||||
#endif
|
||||
texture = mEmptyTexture.get();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -112,6 +112,8 @@ private:
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
VulkanReadPixels mReadPixels;
|
||||
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -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<uint32_t>(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<bool>(arg)) {
|
||||
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
|
||||
} else if (std::holds_alternative<float>(arg)) {
|
||||
*((float*)addr) = std::get<float>(arg);
|
||||
} else {
|
||||
*((int32_t*)addr) = std::get<int32_t>(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<bool>(arg)) {
|
||||
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
|
||||
} else if (std::holds_alternative<float>(arg)) {
|
||||
*((float*)addr) = std::get<float>(arg);
|
||||
} else {
|
||||
*((int32_t*)addr) = std::get<int32_t>(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)
|
||||
|
||||
@@ -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<CustomSamplerInfo>;
|
||||
|
||||
// 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<uint16_t> 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<uint16_t> bindingToSamplerIndex;
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = {VK_NULL_HANDLE};
|
||||
VkSpecializationInfo specializationInfo = {};
|
||||
utils::FixedCapacityVector<VkSpecializationMapEntry> specConsts;
|
||||
std::unique_ptr<char[]> 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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -91,6 +91,7 @@ public:
|
||||
|
||||
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 @@ public:
|
||||
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 @@ private:
|
||||
RasterState mCurrentRasterState;
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
DescriptorKey mDescriptorRequirements = {};
|
||||
VkSpecializationInfo* mSpecializationRequirements = {};
|
||||
VkSpecializationInfo const* mSpecializationRequirements = nullptr;
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ private:
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)); }
|
||||
|
||||
@@ -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<OutType> enumerate(
|
||||
#undef EXPAND_ENUM_NO_ARGS
|
||||
#undef EXPAND_ENUM_ARGS
|
||||
|
||||
|
||||
// Useful shorthands
|
||||
using VkFormatList = utils::FixedCapacityVector<VkFormat>;
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANUTILITY_H
|
||||
|
||||
@@ -420,9 +420,9 @@ inline int deviceTypeOrder(VkPhysicalDeviceType deviceType) {
|
||||
}
|
||||
|
||||
VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
VulkanPlatform::GPUPreference const& gpuPreference) {
|
||||
FixedCapacityVector<VkPhysicalDevice> const physicalDevices
|
||||
= filament::backend::enumerate(vkEnumeratePhysicalDevices, instance);
|
||||
VulkanPlatform::Customization::GPUPreference const& gpuPreference) {
|
||||
FixedCapacityVector<VkPhysicalDevice> 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<VkFormat> 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);
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
// Platform specific includes and defines
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/native_window.h>
|
||||
#elif defined(__linux__) && defined(FILAMENT_SUPPORTS_GGP)
|
||||
#include <ggp_c/ggp.h>
|
||||
#elif defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
#include <dlfcn.h>
|
||||
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<PFN_vkCreateStreamDescriptorSurfaceGGP>(
|
||||
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<wl*>(nativeWindow);
|
||||
extent.width = ptrval->width;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace {
|
||||
|
||||
std::tuple<VkImage, VkDeviceMemory> 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<VkImage, VkDeviceMemory> 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<VkSurfaceFormatKHR> const surfaceFormats
|
||||
= enumerate(vkGetPhysicalDeviceSurfaceFormatsKHR, mPhysicalDevice, mSurface);
|
||||
FixedCapacityVector<VkFormat> expectedFormats
|
||||
= {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM};
|
||||
std::array<VkFormat, 2> 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() {
|
||||
|
||||
@@ -108,6 +108,7 @@ private:
|
||||
VkExtent2D const mFallbackExtent;
|
||||
|
||||
bool mUsesRGB = false;
|
||||
bool mHasStencil = false;
|
||||
bool mSuboptimal;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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 @@ protected:
|
||||
|
||||
|
||||
public:
|
||||
FilamentFixture() {
|
||||
FilamentCullingFixture() {
|
||||
|
||||
std::default_random_engine gen; // NOLINT
|
||||
std::uniform_real_distribution<float> rand(-100.0f, 100.0f);
|
||||
@@ -75,12 +75,12 @@ public:
|
||||
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) {
|
||||
|
||||
@@ -110,6 +110,10 @@ public:
|
||||
* @return The maximum capacity of the BufferObject.
|
||||
*/
|
||||
size_t getByteCount() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~BufferObject() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <math/mathfwd.h>
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
#include <math/mat4.h>
|
||||
|
||||
namespace utils {
|
||||
class Entity;
|
||||
@@ -172,6 +173,30 @@ public:
|
||||
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<double>::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<double>::infinity());
|
||||
|
||||
|
||||
/** Sets the projection matrix from a frustum defined by six planes.
|
||||
*
|
||||
* @param projection type of #Projection to use.
|
||||
@@ -209,7 +234,8 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
*/
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
*
|
||||
* \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 @@ public:
|
||||
* @return effective full field of view in degrees
|
||||
*/
|
||||
static double computeEffectiveFov(double fovInDegrees, double focusDistance) noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Camera() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -478,6 +478,10 @@ public:
|
||||
private:
|
||||
friend class FColorGrading;
|
||||
};
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~ColorGrading() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -67,15 +67,28 @@ public:
|
||||
* @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<typename T>
|
||||
inline T* getPropertyAddress(const char* name) noexcept {
|
||||
inline T* getPropertyAddress(const char* name) {
|
||||
return static_cast<T*>(getPropertyAddress(name));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool getPropertyAddress(const char* name, T** p) noexcept {
|
||||
inline T const* getPropertyAddress(const char* name) const noexcept {
|
||||
return static_cast<T*>(getPropertyAddress(name));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool getPropertyAddress(const char* name, T** p) {
|
||||
*p = getPropertyAddress<T>(name);
|
||||
return *p != nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool getPropertyAddress(const char* name, T* const* p) const noexcept {
|
||||
*p = getPropertyAddress<T>(name);
|
||||
return *p != nullptr;
|
||||
}
|
||||
@@ -129,6 +142,10 @@ public:
|
||||
float pid_i = 0.0f;
|
||||
float pid_d = 0.0f;
|
||||
};
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~DebugRegistry() = default;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -268,6 +268,19 @@ public:
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -75,6 +75,10 @@ public:
|
||||
* FenceStatus::ERROR otherwise.
|
||||
*/
|
||||
static FenceStatus waitAndDestroy(Fence* fence, Mode mode = Mode::FLUSH);
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Fence() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -49,8 +49,6 @@ public:
|
||||
// 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<typename T>
|
||||
|
||||
@@ -118,6 +118,10 @@ public:
|
||||
* @return The number of indices the IndexBuffer holds.
|
||||
*/
|
||||
size_t getIndexCount() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~IndexBuffer() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -342,6 +342,10 @@ public:
|
||||
/** @deprecated use static versions instead */
|
||||
UTILS_DEPRECATED
|
||||
math::float4 getColorEstimate(math::float3 direction) const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~IndirectLight() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -91,6 +91,10 @@ public:
|
||||
* @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
|
||||
|
||||
@@ -143,20 +143,13 @@ public:
|
||||
using Instance = utils::EntityInstance<LightManager>;
|
||||
|
||||
/**
|
||||
* 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 @@ public:
|
||||
*/
|
||||
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 @@ public:
|
||||
*/
|
||||
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<typename F>
|
||||
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
|
||||
|
||||
@@ -375,6 +375,10 @@ public:
|
||||
|
||||
//! Returns this material's default instance.
|
||||
MaterialInstance const* getDefaultInstance() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Material() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -479,6 +479,10 @@ public:
|
||||
*/
|
||||
void setStencilWriteMask(uint8_t writeMask,
|
||||
StencilFace face = StencilFace::FRONT_AND_BACK) noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~MaterialInstance() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -136,6 +136,10 @@ public:
|
||||
* @return The number of targets the MorphTargetBuffer holds.
|
||||
*/
|
||||
size_t getCount() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~MorphTargetBuffer() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -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.
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -180,6 +180,10 @@ public:
|
||||
* @return Number of color attachments usable in a render target.
|
||||
*/
|
||||
uint8_t getSupportedColorAttachmentsCount() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~RenderTarget() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -102,6 +102,29 @@ public:
|
||||
*/
|
||||
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 @@ public:
|
||||
typename = typename is_supported_index_type<INDEX>::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,
|
||||
|
||||
@@ -173,6 +173,12 @@ public:
|
||||
*/
|
||||
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 @@ public:
|
||||
* getUserTime()
|
||||
*/
|
||||
void resetUserTime();
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Renderer() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -140,16 +140,22 @@ public:
|
||||
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 @@ public:
|
||||
* @param functor User provided functor called for each entity in the scene
|
||||
*/
|
||||
void forEach(utils::Invocable<void(utils::Entity entity)>&& functor) const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Scene() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -115,6 +115,10 @@ public:
|
||||
* @return The number of bones the SkinningBuffer holds.
|
||||
*/
|
||||
size_t getBoneCount() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~SkinningBuffer() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -174,6 +174,10 @@ public:
|
||||
* @return the associated texture, or null if it does not exist
|
||||
*/
|
||||
Texture const* getTexture() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Skybox() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -207,6 +207,10 @@ public:
|
||||
* @return timestamp in nanosecond.
|
||||
*/
|
||||
int64_t getTimestamp() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Stream() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -286,6 +286,10 @@ public:
|
||||
void setFrameCompletedCallback(backend::CallbackHandler* handler = nullptr,
|
||||
FrameCompletedCallback&& callback = {}) noexcept;
|
||||
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~SwapChain() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -541,6 +541,10 @@ public:
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~Texture() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -118,6 +118,29 @@ public:
|
||||
*/
|
||||
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 @@ public:
|
||||
* 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 @@ public:
|
||||
* 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 @@ public:
|
||||
* @see openLocalTransformTransaction(), setTransform()
|
||||
*/
|
||||
void commitLocalTransformTransaction() noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~TransformManager() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -207,6 +207,10 @@ public:
|
||||
* @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
|
||||
|
||||
@@ -894,6 +894,10 @@ public:
|
||||
*/
|
||||
UTILS_DEPRECATED
|
||||
AmbientOcclusion getAmbientOcclusion() const noexcept;
|
||||
|
||||
protected:
|
||||
// prevent heap allocation
|
||||
~View() = default;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
+28
-49
@@ -22,49 +22,28 @@ namespace filament {
|
||||
|
||||
using namespace math;
|
||||
|
||||
template<typename T>
|
||||
details::TMat44<T> inverseProjection(const details::TMat44<T>& p) noexcept {
|
||||
details::TMat44<T> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<HwTexture> texture,
|
||||
PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms);
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareShadowPCFDebug(Handle<HwTexture> 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);
|
||||
|
||||
@@ -128,6 +128,9 @@ public:
|
||||
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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<float>::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{
|
||||
|
||||
@@ -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
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,70 +23,82 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
|
||||
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<Entity> 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<FCamera>(engine, entity);
|
||||
// For historical reasons, FCamera must not move. So the CameraManager stores a pointer.
|
||||
FCamera* const camera = engine.getHeapAllocator().make<FCamera>(engine, entity);
|
||||
manager.elementAt<CAMERA>(i) = camera;
|
||||
manager.elementAt<OWNS_TRANSFORM_COMPONENT>(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<OWNS_TRANSFORM_COMPONENT>(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<CAMERA>(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<OWNS_TRANSFORM_COMPONENT>(i);
|
||||
|
||||
{ // scope for camera -- it's invalid after this scope.
|
||||
FCamera* const camera = manager.elementAt<CAMERA>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ public:
|
||||
~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 @@ public:
|
||||
}
|
||||
|
||||
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<CAMERA>(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<FCamera *>;
|
||||
using Base = utils::SingleInstanceComponentManager<FCamera*, bool>;
|
||||
|
||||
struct CameraManagerImpl : public Base {
|
||||
using Base::gc;
|
||||
using Base::swap;
|
||||
using Base::hasComponent;
|
||||
} mManager;
|
||||
|
||||
FEngine& mEngine;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user