Merge branch 'rc/1.45.1' into release

This commit is contained in:
Benjamin Doherty
2023-11-09 10:09:56 -08:00
179 changed files with 4830 additions and 2818 deletions
+7 -5
View File
@@ -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)
+2 -2
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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.
+11 -19
View File
@@ -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);
}
}
+1 -1
View File
@@ -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>
+1
View File
@@ -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
+15 -2
View File
@@ -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);
}
}
+8 -6
View File
@@ -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;
+1 -1
View File
@@ -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
+37 -25
View File
@@ -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];
+25 -7
View File
@@ -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 {
+73 -94
View File
@@ -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
+35 -40
View File
@@ -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
+32 -24
View File
@@ -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
+4 -3
View File
@@ -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();
+6 -4
View File
@@ -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;
+58 -64
View File
@@ -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
+71 -59
View File
@@ -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)
+54 -4
View File
@@ -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
+8 -14
View File
@@ -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)); }
+7 -1
View File
@@ -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;
};
+24 -5
View File
@@ -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
```
+5 -5
View File
@@ -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) {
+4
View File
@@ -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
+46 -54
View File
@@ -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
+4
View File
@@ -478,6 +478,10 @@ public:
private:
friend class FColorGrading;
};
protected:
// prevent heap allocation
~ColorGrading() = default;
};
} // namespace filament
+20 -3
View File
@@ -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;
};
+13
View File
@@ -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.
*
+4
View File
@@ -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
-2
View File
@@ -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>
+4
View File
@@ -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
+22 -21
View File
@@ -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
+4
View File
@@ -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
+2 -1
View File
@@ -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.
};
/**
+4
View File
@@ -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,
+10
View File
@@ -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
+14 -4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -207,6 +207,10 @@ public:
* @return timestamp in nanosecond.
*/
int64_t getTimestamp() const noexcept;
protected:
// prevent heap allocation
~Stream() = default;
};
} // namespace filament
+4
View File
@@ -286,6 +286,10 @@ public:
void setFrameCompletedCallback(backend::CallbackHandler* handler = nullptr,
FrameCompletedCallback&& callback = {}) noexcept;
protected:
// prevent heap allocation
~SwapChain() = default;
};
} // namespace filament
+4
View File
@@ -541,6 +541,10 @@ public:
return *this;
}
};
protected:
// prevent heap allocation
~Texture() = default;
};
} // namespace filament
+24
View File
@@ -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
+29 -2
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -894,6 +894,10 @@ public:
*/
UTILS_DEPRECATED
AmbientOcclusion getAmbientOcclusion() const noexcept;
protected:
// prevent heap allocation
~View() = default;
};
} // namespace filament
+28 -49
View File
@@ -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
+2 -2
View File
@@ -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
+5 -1
View File
@@ -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);
}
+12 -4
View File
@@ -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 {
+1 -1
View File
@@ -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);
+12 -1
View File
@@ -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);
+3
View File
@@ -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;
+1 -1
View File
@@ -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) {
+16
View File
@@ -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);
+4
View File
@@ -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));
}
+4
View File
@@ -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();
}
+7 -6
View File
@@ -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;
+54 -10
View File
@@ -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{
+100
View File
@@ -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
//------------------------------------------------------------------------------
+27 -11
View File
@@ -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
+40 -28
View File
@@ -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);
}
}
}
+24 -9
View File
@@ -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