Compare commits

..
Author SHA1 Message Date
Powei Feng 69fc1f302c vk: testing a robin_map key hash failure
Executable on device with:

./build.sh -p android -q arm64-v8a release test_compiler && adb push out/cmake-android-release-aarch64/filament/test/test_compiler /data/local/tmp/ && adb shell chmod 777 /data/local/tmp/test_compiler && adb shell /data/local/tmp/test_compiler
2024-07-26 12:23:28 +08:00
Mathias Agopian e7c96cd124 better handle skipped frames and cache eviction
Add Renderer::skipFrame() which should be called when intentionally 
skipping frames, for instance because the screen content hasn't changed,
allowing Filament to performance needed periodic tasks, such as
cache garbage collection and callback dispatches.

We also improve the ResourceAllocator cache eviction policy:
- the cache is aggressively purged when skipping a frame
- we aggressively evict entries older than 3 frames

The default Config is now set to a more agressive setting.
2024-07-25 17:23:04 -07:00
Mathias Agopian 730bc99025 Move the ResourceAllocator from Engine to Renderer
The ResourceAllocator used to be global and owned by the Engine, this
was causing some issues when using several Renderers because each
one could cause the eviction of cache data for another.

We now have a ResourceAllocator per Renderer, which makes more sense
because most resources are allocated by the FrameGraph.

We also introduce a ResourceAllocatorDisposer class, which is used
for checking in and out a texture from the cache, and destroy the
texture when it's checked-out. That objet is still global.
2024-07-25 17:23:04 -07:00
Powei Feng 7441e878bb gltfio: enable escaped unicode for node name (#7989)
Fixes #7846
2024-07-25 09:06:56 +00:00
Grant CommodoreandPowei Feng ef4c4a76fc Only sets the layercount if the view has stereo and the stereoscopic type is multiview (#7987)
Co-authored-by: Powei Feng <powei@google.com>
2024-07-25 07:31:51 +00:00
Mathias Agopian be4f287b07 More improvements to the JobSystem (#7988)
* improve parallel_for a bit

We get about 40% performance increase. The gain comes from not having
to copy the JobData structure each time we create a job, by using a
new emplaceJob() method, we can create the structure directly into
its destination.

* avoid calling wakeAll() when possible

wakeAll() is very expensive and not always needed when a job finishes
because there may not be anyone waiting on that job.

We now maintain a waiter count per job, and use that to determine if
we need to notify or not.

And now that the JobSystem overhead is lower, we can decrease the size
of the jobs, which improves the load balancing.

* mActiveJobs fixes

some comments claimed mActiveJobs needed to be modified before or after
accessing the WorkQueue; this couldn't be correct because there were no
guaranteed global ordering with the workQueue.
2024-07-24 15:34:36 -07:00
Mathias Agopian d3a35de386 fix a crash with bloom when screen dimension smaller than 16px 2024-07-24 11:49:38 -07:00
Sungun Park 9057c47a56 Release Filament 1.53.2 2024-07-23 01:23:57 +00:00
Mathias Agopian 4a5081388d RenderPass batch size was too small
The batch size was calculated incorrectly, which in theory could lead
to command buffer overflows (albeit unlikely).
2024-07-22 13:53:05 -07:00
Mathias Agopian a60fe41681 performance improvements to JobSystem
- reduce the number of calls to notify_one() and notify_all().
  notify_one() is not only called when running a new job, and
  notify_all() only when a job finishes.

- don't hold the condition lock while calling notify_*(), as it is not
  strictly needed, and because notify_*() can be very slow, there can
  be a lot of contention on this lock as a result; blocking the whole
  jobsystem thread pool.

- add a new version of run() that takes an opaque thread id that can
  be retrieved from a job's execute function; this is especially
  intended to be used by parallel_for(); it's just a more efficient
  version of run() that avoids a hashmap lookup.


Overall these change yield a significant performance boost:
- running + waiting a job: +200%
- running many jobs: +150%
- running many jobs in parallel: +50%
2024-07-22 11:12:51 -07:00
Mathias Agopian a8ace2891d improve frame timing info
- we use a circular buffer for the frame history so that 
  we don't have to copy the data when insert a new entry.
  This also allows us to keep a reference to an entry, which
  doesn't get invalidated when an entry is added/removed.

- we now store the gpu frame time in the correct slot (instead of
  always the latest). It didn't matter before because the API wasn't
  public and we only needed some recent frame time.

- a new public API now returns the frame history, which now contains 
  more data; in particular the main and backend thread's begin/end
  frame time.


BUGS=[321110544]
2024-07-18 21:14:44 -07:00
mdagoisandPowei Feng 669ffc85c0 Minor typo fix and slight optimization (#7978)
Co-authored-by: Powei Feng <powei@google.com>
2024-07-19 03:07:54 +00:00
mdagoisandPowei Feng d957dd8082 Fix to barriers in VulkanBuffer (#7979)
Co-authored-by: Powei Feng <powei@google.com>
2024-07-19 10:45:10 +08:00
Ben Doherty 8eda62c957 Update imgui to v1.90.9 (#7977) 2024-07-18 16:38:25 -07:00
Sungun Park 90f079ac2c Add an extra check for renderStandaloneView (#7975)
renderStandaloneView must be called outside of beginFrame() /
endFrame(). This extra check ensures this behavior.
2024-07-18 06:31:02 -07:00
Balaji M 3728f06603 utility::loadCgltfBuffers is done once instead of doing for each primitive (#7969) 2024-07-12 23:22:25 +00:00
Adam Wychowaniec 39cfce641c Fix precondition check when creating static geometry. 2024-07-08 11:53:24 -07:00
Mathias Agopian 3fbf3f7111 fix a couple small bugs when mapping the circular buffer
- in case of failure we were munmap'ing the wrong size for the
  guard page (in practice this never happened)
- the post-condition check was incorrect; it checked for nullptr instead
  of MAP_FAILED. this also never happened in practice.

Also made a couple of small improvements:

- in case the special circular buffer mapping fails, log a message
  as warning instead of debug.

- immediately memset (i.e. populate) the pages for the circular buffer
  since they will all be accessed rather quickly.
2024-07-08 10:48:44 -07:00
Reinar ef9bbece2a Fix feature level detection for VulkanDriver 2024-07-02 15:09:25 -07:00
Mathias Agopian eb8fa9ecdf shader compilation could fail on ES 3.0 devices
the failure could happen if the shader didn't have any #extension
strings, which was likely to happen on release builds (e.g. on 
emulator).
2024-07-01 11:25:12 -07:00
Ben Doherty e1bfe7ca81 Metal: add a more detailed CHECK_POSTCONDITION when creating a texture (#7943) 2024-06-28 13:11:00 -07:00
Ben Doherty ca4c7ac739 Update cgltf to 1.14 (#7945) 2024-06-28 13:10:41 -07:00
Ben Doherty 9676bc94e8 Fix MetalBumpAllocator (#7951) 2024-06-28 13:09:01 -07:00
Mathias Agopian f7a5111106 switch to new morphing API
- remove deprecated morphing APIs
- repair gltfio, samples and tests

The new API doesn't allow a MorphTargetBuffer per RenderPrimitive,
instead the MorphTargetBuffer is specified per Renderable.

gltfio separates RenderPrimitives from Renderables, in particular all
RenderPrimitives are created before their Renderable; this was
problematic for this change because all primitives must share
a single MorphTargetBuffer living in the Renderable.

To fix this, we're no longer initializing the morphing paramters
at RenderPrimitive creation, instead we store a reference to the
BufferSlot in the Primtive structure, so that later, when the Renderable
is created we can finally retrieve the BufferSlot and initialize its
morphing paramters, which are not available. The "morphing parameters"
are now expanded to contain the MorphTargetBuffer as before (except now
it's always the same for all the primitives of a Rendrable), as well
as the offset within the buffer and the vertex count.
2024-06-28 12:14:57 -07:00
Sungun Park 56ac7ab902 IBL drag & drop support for desktop gltf_viewer (#7947)
Users now can drag and drop IBL files for desktop gltf_viewer.

It attempts to load gltf files first then tries to load IBL files.
2024-06-27 23:14:13 +00:00
Ben Doherty 44b4de904e Add saveRawVariants flag to matc (#7949) 2024-06-27 14:36:03 -07:00
Tibor Hencz a2fe9f745c Add ability to disable panning in the OrbitManipulator (#7928) 2024-06-27 00:05:14 +00:00
Benjamin Doherty 659b8b6e03 Release Filament 1.53.1 2024-06-25 11:46:46 -07:00
Ben Doherty c73f4e64d5 Metal: use a bump allocator for vertex and index uploads (#7926) 2024-06-25 11:45:45 -07:00
Mathias Agopian fe743523bf fix use-after-free dereference risk
Texture handles were resolved to pointers when updating a SamplerGroup,
as that point the handle was checked for use-after-free. However, the
texture could be destroyed later while still active in the SamplerGroup,
this would result in using the pointer which now contains garbage.

We now keep the handle and resolve the texture when binding samplers
to the program; which will also perform the use-after-free check.
2024-06-24 16:16:12 -07:00
Ryan 2c1044e812 Fix use-after-free of a std::mutex in PresentCallable. (#7934)
If a user is using `setFrameScheduledCallback()`, managing the provided PresentCallable during engine shutdown is tricky -- we'll likely get a final frame scheduled when we flush the engine's work queue, but the PresentCallable will schedule the final CAMetalDrawable to be released on main thread afterwards, even if we call `present(false)` to skip it. If the swap chain is destroyed before that main queue block gets executed, the mutex presenting that drawable will no longer exist, causing a crash.

To make things easier, store the std::mutex in a shared_ptr, so that a PresentCallable can safely outlive the FSwapChain instance that created it and clean itself up afterwards.

Alternatives considered, all of which seem unfortunate:
* Require users to clear out the callback before shutting down the engine, so that any final drawables are immediately discarded instead of using PresentCallable
* Require users to split up the engine teardown across two main queue blocks, ensuring that the PresentCallable cleanup executes before swapchains are destroyed
* Drop the PresentCallable on the ground and leak the memory
2024-06-24 11:00:58 -07:00
Sungun Park db9183a105 Fix memory leak of asset loading for gltf_viewer (#7933)
When a user drags and drops a gltf file to the gltf_viewer window, it
loads the new asset.

While this happens, the function `checkAsset` tries parsing the gltf
file, but it doesn't free it. Fix this.
2024-06-21 10:37:01 -07:00
e4a0bb8fa0 Add support for KHR_materials_Specular (#7564)
Co-authored-by: Todd Zhang <toddzx@amazon.com>
Co-authored-by: Romain Guy <romain.guy@gmail.com>
Co-authored-by: Mathias Agopian <mathias@google.com>
2024-06-21 17:06:17 +00:00
Mathias Agopian d15c53e530 an easier way to pipe vsync time to beginFrame
BUGS=[343764098]
2024-06-21 09:41:19 -07:00
Mathias Agopian cc8a3ed96b fix typo that broke glsl compilation on device 2024-06-20 15:48:17 -07:00
Mathias Agopian 213eb6af9e Lit Materials can now specify the quality of SH computations
The number of SH bands used for the indirect light irradiance
computations can be set to 1, 2 or 3 (default) in Material::Builder.

For e.g. in lower-end devices w/ non HDR content, it might be
beneficial to set this value to 2.

BUGS=[341971013]
2024-06-20 10:25:04 -07:00
Mathias Agopian e04c6d406f inject the missing packing/unpacking function in ESSL 3.0
FIXES=[343784713]
2024-06-18 13:37:20 -07:00
Mathias Agopian cab799f531 matdbg would cause a crash if program was invalid 2024-06-18 00:27:24 -07:00
Benjamin Doherty e95edef9d7 Release Filament 1.53.0 2024-06-17 17:26:47 -07:00
Grant Commodore 9cfef925bb Updates vulkan swapchain synchronization (#7915)
Abstracts the synchronization of the vulkan swapchain so that it is easier to override during the acquisition and presentation of images. A new structure (ImageSyncData) is created to hold swapchain synchronization data.

FIXES=[338303279]
2024-06-17 22:29:28 +00:00
Hanno J. Gödecke 3900fc6c37 feat: support getParameter from MaterialInstance (#7686) 2024-06-17 11:31:17 -07:00
Powei Feng b8ec1658f3 vk: single output stream debug logging (#7925)
In some debug instances, it's nice to have the logs all go to one
output stream so that logs are ordered. We add a FVK_DEBUG flag
for this use case.
2024-06-14 22:31:45 +00:00
Grant Commodore 371bd1f01f Checks/enable if the multiview extension is supported (#7923)
Checks if the vulkan driver supports multiview. The result is stored in the vulkan context.

BUG=[332425392]
FIXES=[346860138]
2024-06-13 10:20:24 -07:00
352 changed files with 32330 additions and 13756 deletions
+3 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.53.0'
implementation 'com.google.android.filament:filament-android:1.53.2'
}
```
@@ -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.53.0'
pod 'Filament', '~> 1.53.2'
```
### Snapshots
@@ -176,6 +176,7 @@ steps:
- [x] KHR_materials_unlit
- [x] KHR_materials_variants
- [x] KHR_materials_volume
- [x] KHR_materials_specular
- [x] KHR_mesh_quantization
- [x] KHR_texture_basisu
- [x] KHR_texture_transform
+10
View File
@@ -7,6 +7,16 @@ 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.53.3
- Add drag and drop support for IBL files for desktop gltf_viewer.
## v1.53.2
## v1.53.1
## v1.53.0
- engine: fix skinning normals with large transforms (b/342459864) [⚠️ **New Material Version**]
@@ -571,3 +571,9 @@ Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nat
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
return (jlong) builder->build();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Engine_getSteadyClockTimeNano(JNIEnv *env, jclass clazz) {
return (jlong)Engine::getSteadyClockTimeNano();
}
@@ -25,12 +25,17 @@ using namespace filament;
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
jlong nativeEngine, jobject buffer_, jint size) {
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount) {
Engine* engine = (Engine*) nativeEngine;
AutoBuffer buffer(env, buffer_, size);
Material* material = Material::Builder()
auto builder = Material::Builder();
if (shBandCount) {
builder.sphericalHarmonicsBandCount(shBandCount);
}
Material* material = builder
.package(buffer.getData(), buffer.getSize())
.build(*engine);
return (jlong) material;
}
@@ -253,16 +253,10 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphingStandard(JNIE
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferAt(JNIEnv*, jclass,
jlong nativeBuilder, int level, int primitiveIndex, jlong nativeMorphTargetBuffer,
int offset, int count) {
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferOffsetAt(JNIEnv*, jclass,
jlong nativeBuilder, int level, int primitiveIndex, int offset) {
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
if (nativeMorphTargetBuffer) {
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count);
} else {
builder->morphing(level, primitiveIndex, offset, count);
}
builder->morphing(level, primitiveIndex, offset);
}
extern "C" JNIEXPORT void JNICALL
@@ -334,18 +328,12 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env,
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEnv*,
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferOffsetAt(JNIEnv*,
jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex,
jlong nativeMorphTargetBuffer, jint offset, jint count) {
jlong, jint offset) {
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
if (nativeMorphTargetBuffer) {
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count);
} else {
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, (size_t) offset, (size_t) count);
}
rm->setMorphTargetBufferOffsetAt((RenderableManager::Instance) i, (uint8_t) level,
(size_t) primitiveIndex, (size_t) offset);
}
extern "C" JNIEXPORT jint JNICALL
@@ -28,6 +28,14 @@
using namespace filament;
using namespace backend;
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong vsyncSteadyClockTimeNano) {
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano));
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer,
jlong nativeSwapChain, jlong frameTimeNanos) {
@@ -187,3 +195,10 @@ Java_com_google_android_filament_Renderer_nSetPresentationTime(JNIEnv *, jclass
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->setPresentationTime(monotonicClockNanos);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Renderer_nSetVsyncTime(JNIEnv *, jclass,
jlong nativeRenderer, jlong steadyClockTimeNano) {
Renderer *renderer = (Renderer *) nativeRenderer;
renderer->setVsyncTime(steadyClockTimeNano);
}
@@ -531,3 +531,12 @@ Java_com_google_android_filament_View_nGetFogEntity(JNIEnv *env, jclass clazz,
View *view = (View *) nativeView;
return (jint)view->getFogEntity().getId();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nClearFrameHistory(JNIEnv *env, jclass clazz,
jlong nativeView, jlong nativeEngine) {
View *view = (View *) nativeView;
Engine *engine = (Engine *) nativeEngine;
view->clearFrameHistory(*engine);
}
@@ -425,9 +425,12 @@ public class Engine {
public long resourceAllocatorCacheSizeMB = 64;
/*
* This value determines for how many frames are texture entries kept in the cache.
* This value determines how many frames texture entries are kept for in the cache. This
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
* Typically only one texture is evicted per frame.
* The default is 1.
*/
public long resourceAllocatorCacheMaxAge = 2;
public long resourceAllocatorCacheMaxAge = 1;
/*
* Disable backend handles use-after-free checks.
@@ -1292,7 +1295,6 @@ public class Engine {
/**
* Switch the command queue to unprotected mode. Protected mode can be activated via
* Renderer::beginFrame() using a protected SwapChain.
* @see Renderer
* @see SwapChain
*/
@@ -1300,6 +1302,13 @@ public class Engine {
nUnprotected(getNativeObject());
}
/**
* Get the current time. This is a convenience function that simply returns the
* time in nanosecond since epoch of std::chrono::steady_clock.
* @return current time in nanosecond since epoch of std::chrono::steady_clock.
* @see Renderer#beginFrame
*/
public static native long getSteadyClockTimeNano();
@UsedByReflection("TextureHelper.java")
public long getNativeObject() {
@@ -346,6 +346,7 @@ public class Material {
public static class Builder {
private Buffer mBuffer;
private int mSize;
private int mShBandCount = 0;
/**
* Specifies the material data. The material data is a binary blob produced by
@@ -361,6 +362,22 @@ public class Material {
return this;
}
/**
* Sets the quality of the indirect lights computations. This is only taken into account
* if this material is lit and in the surface domain. This setting will affect the
* IndirectLight computation if one is specified on the Scene and Spherical Harmonics
* are used for the irradiance.
*
* @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default).
* @return Reference to this Builder for chaining calls.
* @see IndirectLight
*/
@NonNull
public Builder sphericalHarmonicsBandCount(@IntRange(from = 0) int shBandCount) {
mShBandCount = shBandCount;
return this;
}
/**
* Creates and returns the Material object.
*
@@ -372,7 +389,8 @@ public class Material {
*/
@NonNull
public Material build(@NonNull Engine engine) {
long nativeMaterial = nBuilderBuild(engine.getNativeObject(), mBuffer, mSize);
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
mBuffer, mSize, mShBandCount);
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
return new Material(nativeMaterial);
}
@@ -1023,7 +1041,7 @@ public class Material {
mNativeObject = 0;
}
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size);
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount);
private static native long nCreateInstance(long nativeMaterial);
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
private static native long nGetDefaultInstance(long nativeMaterial);
@@ -74,7 +74,7 @@ public class MorphTargetBuffer {
*
* @exception IllegalStateException if the MorphTargetBuffer could not be created
*
* @see #setMorphTargetBufferAt
* @see #setMorphTargetBufferOffsetAt
*/
@NonNull
public MorphTargetBuffer build(@NonNull Engine engine) {
@@ -569,40 +569,13 @@ public class RenderableManager {
*
* @param level the level of detail (lod), only 0 can be specified
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
* @param morphTargetBuffer specifies the morph target buffer
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
*/
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, 0, offset, count);
return this;
}
/** @deprecated */
@Deprecated
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), offset, count);
return this;
}
/** @deprecated */
@Deprecated
@NonNull
public Builder morphing(@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer) {
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
@IntRange(from = 0) int offset) {
nBuilderSetMorphTargetBufferOffsetAt(mNativeBuilder, level, primitiveIndex, offset);
return this;
}
@@ -705,34 +678,11 @@ public class RenderableManager {
*
* @see Builder#morphing
*/
public void setMorphTargetBufferAt(@EntityInstance int i,
public void setMorphTargetBufferOffsetAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, 0, offset, count);
}
/** @deprecated */
@Deprecated
public void setMorphTargetBufferAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer,
@IntRange(from = 0) int offset,
@IntRange(from = 0) int count) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), offset, count);
}
/** @deprecated */
@Deprecated
public void setMorphTargetBufferAt(@EntityInstance int i,
@IntRange(from = 0) int level,
@IntRange(from = 0) int primitiveIndex,
@NonNull MorphTargetBuffer morphTargetBuffer) {
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
@IntRange(from = 0) int offset) {
nSetMorphTargetBufferOffsetAt(mNativeObject, i, level, primitiveIndex, 0, offset);
}
/**
@@ -1033,7 +983,7 @@ public class RenderableManager {
private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset);
private static native void nBuilderMorphing(long nativeBuilder, int targetCount);
private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer);
private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
private static native void nBuilderSetMorphTargetBufferOffsetAt(long nativeBuilder, int level, int primitiveIndex, int offset);
private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled);
private static native void nBuilderFog(long nativeBuilder, boolean enabled);
private static native void nBuilderLightChannel(long nativeRenderableManager, int channel, boolean enable);
@@ -1043,7 +993,7 @@ public class RenderableManager {
private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset);
private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset);
private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights, int offset);
private static native void nSetMorphTargetBufferAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
private static native void nSetMorphTargetBufferOffsetAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset);
private static native int nGetMorphTargetCount(long nativeObject, int i);
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);
@@ -284,6 +284,33 @@ public class Renderer {
nSetPresentationTime(getNativeObject(), monotonicClockNanos);
}
/**
* The use of this method is optional. It sets the VSYNC time expressed as the duration in
* nanosecond since epoch of std::chrono::steady_clock.
* If called, passing 0 to frameTimeNanos in Renderer.BeginFrame will use this
* time instead.
* @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock
* @see Engine#getSteadyClockTimeNano
* @see Renderer#beginFrame
*/
public void setVsyncTime(long steadyClockTimeNano) {
nSetVsyncTime(getNativeObject(), steadyClockTimeNano);
}
/**
* Call skipFrame when momentarily skipping frames, for instance if the content of the
* scene doesn't change.
*
* @param vsyncSteadyClockTimeNano The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by 1000000 to
* convert it to the {@link android.os.SystemClock#uptimeMillis()}
* time base. This typically comes from
* {@link android.view.Choreographer.FrameCallback}.
*/
public void skipFrame(long vsyncSteadyClockTimeNano) {
nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano);
}
/**
* Sets up a frame for this <code>Renderer</code>.
* <p><code>beginFrame</code> manages frame pacing, and returns whether or not a frame should be
@@ -702,6 +729,8 @@ public class Renderer {
}
private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos);
private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano);
private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano);
private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos);
private static native void nEndFrame(long nativeRenderer);
private static native void nRender(long nativeRenderer, long nativeView);
@@ -1233,6 +1233,18 @@ public class View {
return nGetFogEntity(getNativeObject());
}
/**
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
* keeps a history of previous frame renders associated with the Renderer the view was last
* used with. When switching Renderer, it may be necessary to clear that history by calling
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
* being very different.
*/
public void clearFrameHistory(Engine engine) {
nClearFrameHistory(getNativeObject(), engine.getNativeObject());
}
public long getNativeObject() {
if (mNativeObject == 0) {
throw new IllegalStateException("Calling method on destroyed View");
@@ -1294,7 +1306,7 @@ public class View {
private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w);
private static native void nGetMaterialGlobal(long nativeView, int index, float[] out);
private static native int nGetFogEntity(long nativeView);
private static native void nClearFrameHistory(long nativeView, long nativeEngine);
/**
* List of available ambient occlusion techniques.
@@ -125,6 +125,11 @@ extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBu
builder->groundPlane(a, b, c, d);
}
extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderPanning(JNIEnv*, jclass, jlong nativeBuilder, jboolean enabled) {
Builder* builder = (Builder*) nativeBuilder;
builder->panning(enabled);
}
extern "C" JNIEXPORT long Java_com_google_android_filament_utils_Manipulator_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jint mode) {
Builder* builder = (Builder*) nativeBuilder;
return (jlong) builder->build((Mode) mode);
@@ -274,6 +274,17 @@ public class Manipulator {
return this;
}
/**
* Sets whether panning is enabled in the manipulator.
*
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder panning(Boolean enabled) {
nBuilderPanning(mNativeBuilder, enabled);
return this;
}
/**
* Creates and returns the <code>Manipulator</code> object.
*
@@ -483,6 +494,7 @@ public class Manipulator {
private static native void nBuilderFlightPanSpeed(long nativeBuilder, float x, float y);
private static native void nBuilderFlightMoveDamping(long nativeBuilder, float damping);
private static native void nBuilderGroundPlane(long nativeBuilder, float a, float b, float c, float d);
private static native void nBuilderPanning(long nativeBuilder, Boolean enabled);
private static native long nBuilderBuild(long nativeBuilder, int mode);
private static native void nDestroyManipulator(long nativeManip);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.53.0
VERSION_NAME=1.53.2
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -75,6 +75,8 @@ public:
*/
size_t textureUseAfterFreePoolSize = 0;
size_t metalUploadBufferSizeBytes = 512 * 1024;
/**
* Set to `true` to forcibly disable parallel shader compilation in the backend.
* Currently only honored by the GL and Metal backends.
@@ -90,6 +90,20 @@ public:
VkExtent2D extent = {0, 0};
};
struct ImageSyncData {
static constexpr uint32_t INVALID_IMAGE_INDEX = UINT32_MAX;
// The index of the next image as returned by vkAcquireNextImage or equivalent.
uint32_t imageIndex = INVALID_IMAGE_INDEX;
// Semaphore to be signaled once the image is available.
VkSemaphore imageReadySemaphore = VK_NULL_HANDLE;
// A function called right before vkQueueSubmit. After this call, the image must be
// available. This pointer can be null if imageReadySemaphore is not VK_NULL_HANDLE.
std::function<void(SwapChainPtr handle)> explicitImageReadyWait = nullptr;
};
VulkanPlatform();
~VulkanPlatform() override;
@@ -127,6 +141,12 @@ public:
* before recreating the swapchain. Default is true.
*/
bool flushAndWaitOnWindowResize = true;
/**
* Whether the swapchain image should be transitioned to a layout suitable for
* presentation. Default is true.
*/
bool transitionSwapChainImageLayoutForPresent = true;
};
/**
@@ -155,13 +175,10 @@ public:
* corresponding VkImage will be used as the output color attachment. The client should signal
* the `clientSignal` semaphore when the image is ready to be used by the backend.
* @param handle The handle returned by createSwapChain()
* @param clientSignal The semaphore that the client will signal to indicate that the backend
* may render into the image.
* @param index Pointer to memory that will be filled with the index that corresponding
* to an image in the `SwapChainBundle.colors` array.
* @param outImageSyncData The synchronization data used for image readiness
* @return Result of acquire
*/
virtual VkResult acquire(SwapChainPtr handle, VkSemaphore clientSignal, uint32_t* index);
virtual VkResult acquire(SwapChainPtr handle, ImageSyncData* outImageSyncData);
/**
* Present the image corresponding to `index` to the display. The client should wait on
@@ -181,6 +181,18 @@ public:
return static_cast<Dp>(p);
}
template<typename B>
bool is_valid(Handle<B>& handle) {
if (handle && isPoolHandle(handle.getId())) {
auto [p, tag] = handleToPointer(handle.getId());
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t const expectedAge = pNode[-1].age;
return expectedAge == age;
}
return true;
}
template<typename Dp, typename B>
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
+9 -6
View File
@@ -32,10 +32,11 @@
# define HAS_MMAP 0
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace utils;
@@ -81,6 +82,9 @@ void* CircularBuffer::alloc(size_t size) noexcept {
// map the circular buffer once...
vaddr = mmap(reserve_vaddr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (vaddr != MAP_FAILED) {
// populate the address space with pages (because this is a circular buffer,
// all the pages will be allocated eventually, might as well do it now)
memset(vaddr, 0, size);
// and map the circular buffer again, behind the previous copy...
vaddr_shadow = mmap((char*)vaddr + size, size,
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
@@ -101,7 +105,7 @@ void* CircularBuffer::alloc(size_t size) noexcept {
if (UTILS_UNLIKELY(mAshmemFd < 0)) {
// ashmem failed
if (vaddr_guard != MAP_FAILED) {
munmap(vaddr_guard, size);
munmap(vaddr_guard, BLOCK_SIZE);
}
if (vaddr_shadow != MAP_FAILED) {
@@ -119,12 +123,11 @@ void* CircularBuffer::alloc(size_t size) noexcept {
data = mmap(nullptr, size * 2 + BLOCK_SIZE,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
FILAMENT_CHECK_POSTCONDITION(data) <<
FILAMENT_CHECK_POSTCONDITION(data != MAP_FAILED) <<
"couldn't allocate " << (size * 2 / 1024) <<
" KiB of virtual address space for the command buffer";
slog.d << "WARNING: Using soft CircularBuffer (" << (size * 2 / 1024) << " KiB)"
<< io::endl;
slog.w << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)" << io::endl;
// guard page at the end
void* guard = (void*)(uintptr_t(data) + size * 2);
+9
View File
@@ -204,6 +204,15 @@ public:
private:
enum class UploadStrategy {
POOL,
BUMP_ALLOCATOR,
};
void uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const;
void uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const;
UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer;
size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
+58 -23
View File
@@ -27,7 +27,16 @@ MetalPlatform* TrackedMetalBuffer::platform = nullptr;
MetalPlatform* ScopedAllocationTimer::platform = nullptr;
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
size_t size, bool forceGpuBuffer)
: mBufferSize(size), mContext(context) {
const MetalBumpAllocator& allocator = *mContext.bumpAllocator;
// VERTEX is also used for index buffers
if (allocator.getCapacity() > 0 && bindingType == BufferObjectBinding::VERTEX) {
mUploadStrategy = UploadStrategy::BUMP_ALLOCATOR;
} else {
mUploadStrategy = UploadStrategy::POOL;
}
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
// This won't work for SSBOs, since they are read/write.
@@ -61,34 +70,23 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(size + byteOffset <= mBufferSize)
<< "Attempting to copy " << size << " bytes into a buffer of size " << mBufferSize
<< " at offset " << byteOffset;
// The copy blit requires that byteOffset be a multiple of 4.
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) << "byteOffset must be a multiple of 4";
// Either copy into the Metal buffer or into our cpu buffer.
// If we have a cpu buffer, we can directly copy into it.
if (mCpuBuffer) {
memcpy(static_cast<uint8_t*>(mCpuBuffer) + byteOffset, src, size);
return;
}
// Acquire a staging buffer to hold the contents of this update.
MetalBufferPool* bufferPool = mContext.bufferPool;
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
memcpy(staging->buffer.get().contents, src, size);
// The blit below requires that byteOffset be a multiple of 4.
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3u)) << "byteOffset must be a multiple of 4";
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit";
[blitEncoder copyFromBuffer:staging->buffer.get()
sourceOffset:0
toBuffer:mBuffer.get()
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
bufferPool->releaseBuffer(staging);
}];
switch (mUploadStrategy) {
case UploadStrategy::BUMP_ALLOCATOR:
uploadWithBumpAllocator(src, size, byteOffset);
break;
case UploadStrategy::POOL:
uploadWithPoolBuffer(src, size, byteOffset);
break;
}
}
void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset) {
@@ -199,5 +197,42 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
}
}
void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const {
MetalBufferPool* bufferPool = mContext.bufferPool;
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
memcpy(staging->buffer.get().contents, src, size);
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit - pool buffer";
[blitEncoder copyFromBuffer:staging->buffer.get()
sourceOffset:0
toBuffer:mBuffer.get()
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
bufferPool->releaseBuffer(staging);
}];
}
void MetalBuffer::uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const {
MetalBumpAllocator& allocator = *mContext.bumpAllocator;
auto [buffer, offset] = allocator.allocateStagingArea(size);
memcpy(static_cast<char*>(buffer.contents) + offset, src, size);
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit - bump allocator";
[blitEncoder copyFromBuffer:buffer
sourceOffset:offset
toBuffer:mBuffer.get()
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
}
} // namespace backend
} // namespace filament
@@ -38,6 +38,28 @@ struct MetalBufferPoolEntry {
mutable uint32_t referenceCount;
};
class MetalBumpAllocator {
public:
MetalBumpAllocator(id<MTLDevice> device, size_t capacity);
/**
* Allocates a staging area of the given size. Returns a pair of the buffer and the offset
* within the buffer. The buffer is guaranteed to be at least the given size, but may be larger.
* Clients must not write to the buffer beyond the returned offset + size.
* Clients are responsible for holding a reference to the returned buffer.
* Allocations are guaranteed to be aligned to 4 bytes.
*/
std::pair<id<MTLBuffer>, size_t> allocateStagingArea(size_t size);
size_t getCapacity() const noexcept { return mCapacity; }
private:
id<MTLDevice> mDevice;
TrackedMetalBuffer mCurrentUploadBuffer = nil;
size_t mHead = 0;
size_t mCapacity;
};
// Manages a pool of Metal buffers, periodically releasing ones that have been unused for awhile.
class MetalBufferPool {
public:
@@ -116,5 +116,39 @@ void MetalBufferPool::reset() noexcept {
mFreeStages.clear();
}
MetalBumpAllocator::MetalBumpAllocator(id<MTLDevice> device, size_t capacity)
: mDevice(device), mCapacity(capacity) {
if (mCapacity > 0) {
mCurrentUploadBuffer = { [device newBufferWithLength:capacity options:MTLStorageModeShared],
TrackedMetalBuffer::Type::STAGING };
}
}
std::pair<id<MTLBuffer>, size_t> MetalBumpAllocator::allocateStagingArea(size_t size) {
if (size == 0) {
return { nil, 0 };
}
if (size > mCapacity) {
return { [mDevice newBufferWithLength:size options:MTLStorageModeShared], 0 };
}
assert_invariant(mCurrentUploadBuffer);
// Align the head to a 4-byte boundary.
mHead = (mHead + 3) & ~3;
if (UTILS_LIKELY(mHead + size <= mCapacity)) {
const size_t oldHead = mHead;
mHead += size;
return { mCurrentUploadBuffer.get(), oldHead };
}
// We're finished with the current allocation.
mCurrentUploadBuffer = { [mDevice newBufferWithLength:mCapacity options:MTLStorageModeShared],
TrackedMetalBuffer::Type::STAGING };
mHead = size;
return { mCurrentUploadBuffer.get(), 0 };
}
} // namespace backend
} // namespace filament
@@ -44,6 +44,7 @@ namespace backend {
class MetalDriver;
class MetalBlitter;
class MetalBufferPool;
class MetalBumpAllocator;
class MetalRenderTarget;
class MetalSamplerGroup;
class MetalSwapChain;
@@ -141,6 +142,7 @@ struct MetalContext {
utils::FixedCircularBuffer<Handle<HwTexture>> texturesToDestroy;
MetalBufferPool* bufferPool;
MetalBumpAllocator* bumpAllocator;
MetalSwapChain* currentDrawSwapChain = nil;
MetalSwapChain* currentReadSwapChain = nil;
@@ -171,6 +171,8 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
mContext->samplerStateCache.setDevice(mContext->device);
mContext->argumentEncoderCache.setDevice(mContext->device);
mContext->bufferPool = new MetalBufferPool(*mContext);
mContext->bumpAllocator =
new MetalBumpAllocator(mContext->device, driverConfig.metalUploadBufferSizeBytes);
mContext->blitter = new MetalBlitter(*mContext);
if (@available(iOS 12, *)) {
@@ -209,6 +211,7 @@ MetalDriver::~MetalDriver() noexcept {
mContext->emptyTexture = nil;
CFRelease(mContext->textureCache);
delete mContext->bufferPool;
delete mContext->bumpAllocator;
delete mContext->blitter;
delete mContext->timerQueryImpl;
delete mContext->shaderCompiler;
+1 -1
View File
@@ -109,7 +109,7 @@ private:
NSUInteger headlessWidth = 0;
NSUInteger headlessHeight = 0;
CAMetalLayer* layer = nullptr;
std::mutex layerDrawableMutex;
std::shared_ptr<std::mutex> layerDrawableMutex;
MetalExternalImage externalImage;
SwapChainType type;
+19 -16
View File
@@ -73,6 +73,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow
: context(context),
depthStencilFormat(decideDepthStencilFormat(flags)),
layer(nativeWindow),
layerDrawableMutex(std::make_shared<std::mutex>()),
externalImage(context),
type(SwapChainType::CAMETALLAYER) {
@@ -179,7 +180,7 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(layerDrawableMutex);
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
@@ -188,8 +189,10 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
}
void MetalSwapChain::releaseDrawable() {
std::lock_guard<std::mutex> lock(layerDrawableMutex);
drawable = nil;
if (drawable) {
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = nil;
}
}
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
@@ -265,7 +268,7 @@ public:
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
std::mutex* drawableMutex, MetalDriver* driver) {
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver) {
assert_invariant(drawableMutex);
assert_invariant(driver);
return new PresentDrawableData(drawable, drawableMutex, driver);
@@ -287,22 +290,22 @@ public:
}
private:
PresentDrawableData(id<CAMetalDrawable> drawable, std::mutex* drawableMutex,
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
MetalDriver* driver)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver) {}
static void cleanupAndDestroy(PresentDrawableData *that) {
{
if (that->mDrawable) {
std::lock_guard<std::mutex> lock(*(that->mDrawableMutex));
that->mDrawable = nil;
}
that->mDrawableMutex = nullptr;
that->mDrawableMutex.reset();
that->mDriver = nullptr;
delete that;
}
id<CAMetalDrawable> mDrawable;
std::mutex* mDrawableMutex = nullptr;
std::shared_ptr<std::mutex> mDrawableMutex;
MetalDriver* mDriver = nullptr;
};
@@ -320,7 +323,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
struct Callback {
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
std::mutex* drawableMutex, MetalDriver* driver)
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver)) {}
std::shared_ptr<FrameScheduledCallback> f;
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
@@ -337,7 +340,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
// This callback pointer will be captured by the block. Even if the scheduled handler is never
// called, the unique_ptr will still ensure we don't leak memory.
__block auto callback = std::make_unique<Callback>(
frameScheduled.callback, drawable, &layerDrawableMutex, context.driver);
frameScheduled.callback, drawable, layerDrawableMutex, context.driver);
backend::CallbackHandler* handler = frameScheduled.handler;
MetalDriver* driver = context.driver;
@@ -553,8 +556,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
FILAMENT_CHECK_POSTCONDITION(texture != nil)
<< "Could not create Metal texture. Out of memory?";
break;
case SamplerType::SAMPLER_CUBEMAP:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
@@ -569,8 +570,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
FILAMENT_CHECK_POSTCONDITION(texture != nil)
<< "Could not create Metal texture. Out of memory?";
break;
case SamplerType::SAMPLER_3D:
descriptor = [MTLTextureDescriptor new];
@@ -583,8 +582,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
descriptor.usage = getMetalTextureUsage(usage);
descriptor.storageMode = MTLStorageModePrivate;
texture = [context.device newTextureWithDescriptor:descriptor];
FILAMENT_CHECK_POSTCONDITION(texture != nil)
<< "Could not create Metal texture. Out of memory?";
break;
case SamplerType::SAMPLER_EXTERNAL:
// If we're using external textures (CVPixelBufferRefs), we don't need to make any
@@ -593,6 +590,12 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
break;
}
FILAMENT_CHECK_POSTCONDITION(target == SamplerType::SAMPLER_EXTERNAL || texture != nil)
<< "Could not create Metal texture (SamplerType = " << int(target)
<< ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat)
<< ", width = " << width << ", height = " << height << ", depth = " << depth
<< "). Out of memory?";
// If swizzling is set, set up a swizzled texture view that we'll use when sampling this texture.
const bool isDefaultSwizzle =
r == TextureSwizzle::CHANNEL_0 &&
+16 -12
View File
@@ -304,6 +304,13 @@ void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept {
void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!mValidProgram)) {
return;
}
#endif
utils::Slice<std::pair<GLint, ConstantType>> constants;
if (stage == ShaderStage::VERTEX) {
constants = mCurrentPushConstants->vertexConstants;
@@ -340,15 +347,11 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
}
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
if (UTILS_UNLIKELY(!p->isValid())) {
// If the program is not valid, we can't call use().
return false;
}
// set-up textures and samplers in the proper TMUs (as specified in setSamplers)
p->use(this, mContext);
bool const success = p->use(this, mContext);
assert_invariant(success == p->isValid());
if (UTILS_UNLIKELY(mContext.isES2())) {
if (UTILS_UNLIKELY(mContext.isES2() && success)) {
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
auto [id, buffer, age] = mContext.getEs2UniformBinding(i);
if (buffer) {
@@ -359,7 +362,8 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
p->setRec709ColorSpace(mRec709OutputColorspace);
}
return true;
return success;
}
@@ -2332,9 +2336,9 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
auto const* const pSamplers = (SamplerDescriptor const*)data.buffer;
for (size_t i = 0, c = sb->textureUnitEntries.size(); i < c; i++) {
GLuint samplerId = 0u;
const GLTexture* t = nullptr;
if (UTILS_LIKELY(pSamplers[i].t)) {
t = handle_cast<const GLTexture*>(pSamplers[i].t);
Handle<HwTexture> th = pSamplers[i].t;
if (UTILS_LIKELY(th)) {
GLTexture const* const t = handle_cast<const GLTexture*>(th);
assert_invariant(t);
SamplerParams params = pSamplers[i].s;
@@ -2390,7 +2394,7 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
// which is not an error.
}
sb->textureUnitEntries[i] = { t, samplerId };
sb->textureUnitEntries[i] = { th, samplerId };
}
scheduleDestroy(std::move(data));
}
+6 -1
View File
@@ -126,7 +126,7 @@ public:
struct GLSamplerGroup : public HwSamplerGroup {
using HwSamplerGroup::HwSamplerGroup;
struct Entry {
GLTexture const* texture = nullptr;
Handle<HwTexture> th;
GLuint sampler = 0u;
};
utils::FixedCapacityVector<Entry> textureUnitEntries;
@@ -256,6 +256,11 @@ private:
return mHandleAllocator.handle_cast<Dp, B>(handle);
}
template<typename B>
bool is_valid(Handle<B>& handle) {
return mHandleAllocator.is_valid(handle);
}
template<typename Dp, typename B>
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
@@ -20,21 +20,24 @@
#include "OpenGLDriver.h"
#include "ShaderCompilerService.h"
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <backend/Handle.h>
#include <private/backend/BackendUtils.h>
#include <utils/debug.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
#include <algorithm>
#include <array>
#include <string_view>
#include <utility>
#include <new>
#include <stddef.h>
#include <stdint.h>
namespace filament::backend {
@@ -241,8 +244,9 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
assert_invariant(sb);
if (!sb) continue; // should never happen, this would be a user error.
for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here
const GLTexture* const t = sb->textureUnitEntries[j].texture;
if (t) { // program may not use all samplers of sampler group
Handle<HwTexture> th = sb->textureUnitEntries[j].th;
if (th) { // program may not use all samplers of sampler group
GLTexture const* const t = gld->handle_cast<GLTexture const*>(th);
gld->bindTexture(tmu, t);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_LIKELY(!es2)) {
+13 -2
View File
@@ -53,11 +53,21 @@ public:
bool isValid() const noexcept { return mToken || gl.program != 0; }
void use(OpenGLDriver* const gld, OpenGLContext& context) noexcept {
if (UTILS_UNLIKELY(!gl.program)) {
bool use(OpenGLDriver* const gld, OpenGLContext& context) noexcept {
// both non-null is impossible by construction
assert_invariant(!mToken || !gl.program);
if (UTILS_UNLIKELY(mToken && !gl.program)) {
// first time a program is used
initialize(*gld);
}
if (UTILS_UNLIKELY(!gl.program)) {
// compilation failed (token should be null)
assert_invariant(!mToken);
return false;
}
context.useProgram(gl.program);
if (UTILS_UNLIKELY(mUsedBindingsCount)) {
// We rely on GL state tracking to avoid unnecessary glBindTexture / glBindSampler
@@ -74,6 +84,7 @@ public:
updateSamplers(gld);
}
return true;
}
// For ES2 only
@@ -359,7 +359,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
GLuint const program = initialize(token);
assert_invariant(token == nullptr);
#ifndef FILAMENT_ENABLE_MATDBG
#if !FILAMENT_ENABLE_MATDBG
assert_invariant(program);
#endif
return program;
@@ -572,16 +572,23 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
// split shader source, so we can insert the specialization constants and the packing
// functions
auto const [prolog, body] = splitShaderSource({ shader_src, shader_len });
auto [version, prolog, body] = splitShaderSource({ shader_src, shader_len });
const std::array<const char*, 4> sources = {
// enable ESSL 3.10 if available
if (context.isAtLeastGLES<3, 1>()) {
version = "#version 310 es\n";
}
const std::array<const char*, 5> sources = {
version.data(),
prolog.data(),
specializationConstantString.c_str(),
packingFunctions.data(),
body.data()
};
const std::array<GLint, 4> lengths = {
const std::array<GLint, 5> lengths = {
(GLint)version.length(),
(GLint)prolog.length(),
(GLint)specializationConstantString.length(),
(GLint)packingFunctions.length(),
@@ -661,6 +668,7 @@ void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context,
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
// macOS doesn't support GL_ARB_shading_language_packing
// Also GLES3.0 didn't have the full set of packing/unpacking functions
std::string_view ShaderCompilerService::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
using namespace std::literals;
#ifdef BACKEND_OPENGL_VERSION_GL
@@ -700,31 +708,102 @@ highp uint packHalf2x16(vec2 v) {
highp uint y = fp32tou16(v.y);
return (y << 16u) | x;
}
highp uint packUnorm4x8(mediump vec4 v) {
v = round(clamp(v, 0.0, 1.0) * 255.0);
highp uint a = uint(v.x);
highp uint b = uint(v.y) << 8;
highp uint c = uint(v.z) << 16;
highp uint d = uint(v.w) << 24;
return (a|b|c|d);
}
highp uint packSnorm4x8(mediump vec4 v) {
v = round(clamp(v, -1.0, 1.0) * 127.0);
highp uint a = uint((int(v.x) & 0xff));
highp uint b = uint((int(v.y) & 0xff)) << 8;
highp uint c = uint((int(v.z) & 0xff)) << 16;
highp uint d = uint((int(v.w) & 0xff)) << 24;
return (a|b|c|d);
}
mediump vec4 unpackUnorm4x8(highp uint v) {
return vec4(float((v & 0x000000ffu) ),
float((v & 0x0000ff00u) >> 8),
float((v & 0x00ff0000u) >> 16),
float((v & 0xff000000u) >> 24)) / 255.0;
}
mediump vec4 unpackSnorm4x8(highp uint v) {
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
}
)"sv;
}
#endif // BACKEND_OPENGL_VERSION_GL
#ifdef BACKEND_OPENGL_VERSION_GLES
if (!context.isES2() && !context.isAtLeastGLES<3, 1>()) {
return R"(
highp uint packUnorm4x8(mediump vec4 v) {
v = round(clamp(v, 0.0, 1.0) * 255.0);
highp uint a = uint(v.x);
highp uint b = uint(v.y) << 8;
highp uint c = uint(v.z) << 16;
highp uint d = uint(v.w) << 24;
return (a|b|c|d);
}
highp uint packSnorm4x8(mediump vec4 v) {
v = round(clamp(v, -1.0, 1.0) * 127.0);
highp uint a = uint((int(v.x) & 0xff));
highp uint b = uint((int(v.y) & 0xff)) << 8;
highp uint c = uint((int(v.z) & 0xff)) << 16;
highp uint d = uint((int(v.w) & 0xff)) << 24;
return (a|b|c|d);
}
mediump vec4 unpackUnorm4x8(highp uint v) {
return vec4(float((v & 0x000000ffu) ),
float((v & 0x0000ff00u) >> 8),
float((v & 0x00ff0000u) >> 16),
float((v & 0xff000000u) >> 24)) / 255.0;
}
mediump vec4 unpackSnorm4x8(highp uint v) {
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
}
)"sv;
}
#endif // BACKEND_OPENGL_VERSION_GLES
return ""sv;
}
// split shader source code in two, the first section goes from the start to the line after the
// last #extension, and the 2nd part goes from there to the end.
std::array<std::string_view, 2> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
auto start = source.find("#version");
assert_invariant(start != std::string_view::npos);
// split shader source code in three:
// - the version line
// - extensions
// - everything else
std::array<std::string_view, 3> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
auto version_start = source.find("#version");
assert_invariant(version_start != std::string_view::npos);
auto pos = source.rfind("\n#extension");
if (pos == std::string_view::npos) {
pos = start;
auto version_eol = source.find('\n', version_start) + 1;
assert_invariant(version_eol != std::string_view::npos);
auto prolog_start = version_eol;
auto prolog_eol = source.rfind("\n#extension"); // last #extension line
if (prolog_eol == std::string_view::npos) {
prolog_eol = prolog_start;
} else {
++pos;
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
}
auto body_start = prolog_eol;
auto eol = source.find('\n', pos) + 1;
assert_invariant(eol != std::string_view::npos);
std::string_view const version = source.substr(start, eol - start);
std::string_view const body = source.substr(version.length(), source.length() - version.length());
return { version, body };
std::string_view const version = source.substr(version_start, version_eol - version_start);
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
std::string_view const body = source.substr(body_start, source.length() - body_start);
return { version, prolog, body };
}
/*
@@ -146,7 +146,7 @@ private:
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
static GLuint linkProgram(OpenGLContext& context,
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
@@ -37,7 +37,7 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
VulkanAttachment src, VulkanAttachment dst,
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
<< " layout=" << dst.getLayout() << utils::io::endl;
@@ -76,7 +76,7 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
VulkanAttachment src, VulkanAttachment dst) {
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
<< " layout=" << dst.getLayout() << utils::io::endl;
@@ -28,6 +28,7 @@ VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
: mAllocator(allocator),
mStagePool(stagePool),
mUsage(usage),
mUpdatedOffset(0),
mUpdatedBytes(0) {
// for now make sure that only 1 bit is set in usage
// (because loadFromCpu() assumes that somewhat)
@@ -80,6 +81,7 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = mGpuBuffer,
.offset = mUpdatedOffset,
.size = mUpdatedBytes,
};
vkCmdPipelineBarrier(cmdbuf, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
@@ -93,6 +95,7 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
};
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, &region);
mUpdatedOffset = byteOffset;
mUpdatedBytes = numBytes;
// Firstly, ensure that the copy finishes before the next draw call.
@@ -42,6 +42,7 @@ private:
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
VkBuffer mGpuBuffer = VK_NULL_HANDLE;
VkBufferUsageFlags mUsage = {};
uint32_t mUpdatedOffset = 0;
uint32_t mUpdatedBytes = 0;
};
@@ -178,7 +178,7 @@ VulkanCommandBuffer& VulkanCommands::get() {
// presenting the swap chain or waiting on a fence.
while (mAvailableBufferCount == 0) {
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
slog.i << "VulkanCommands has stalled. "
FVK_LOGI << "VulkanCommands has stalled. "
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
<< io::endl;
#endif
@@ -289,7 +289,7 @@ bool VulkanCommands::flush() {
};
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
slog.i << "Submitting cmdbuffer=" << cmdbuffer
FVK_LOGI << "Submitting cmdbuffer=" << cmdbuffer
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
<< " signal=" << renderingFinished
<< " fence=" << currentbuf->fence->fence
@@ -305,7 +305,7 @@ bool VulkanCommands::flush() {
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
if (result != VK_SUCCESS) {
utils::slog.d << "Failed command buffer submission result: " << result << utils::io::endl;
FVK_LOGD << "Failed command buffer submission result: " << result << utils::io::endl;
}
#endif
assert_invariant(result == VK_SUCCESS);
@@ -320,7 +320,7 @@ VkSemaphore VulkanCommands::acquireFinishedSignal() {
VkSemaphore semaphore = mSubmissionSignal;
mSubmissionSignal = VK_NULL_HANDLE;
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
FVK_LOGI << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
#endif
return semaphore;
}
@@ -329,7 +329,7 @@ void VulkanCommands::injectDependency(VkSemaphore next) {
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
mInjectedSignal = next;
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
FVK_LOGI << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
#endif
}
@@ -398,7 +398,7 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
// If the timestamp is not 0, then we are carrying over a marker across buffer submits.
// If it is 0, then this is a normal marker push and we should just print debug line as usual.
if (timestamp.time_since_epoch().count() == 0.0) {
utils::slog.d << "----> " << str << utils::io::endl;
FVK_LOGD << "----> " << str << utils::io::endl;
}
#endif
@@ -436,7 +436,7 @@ void VulkanCommands::popGroupMarker() {
auto const [marker, startTime] = mGroupMarkers->pop();
auto const endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = endTime - startTime;
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
<< utils::io::endl;
#else
mGroupMarkers->pop();
@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANCONSTANTS_H
#define TNT_FILAMENT_BACKEND_VULKANCONSTANTS_H
#include <utils/Log.h>
#include <stdint.h>
// In debug builds, we enable validation layers and set up a debug callback.
@@ -70,6 +72,11 @@
// the currently active resources.
#define FVK_DEBUG_RESOURCE_LEAK 0x00010000
// Set this to enable logging "only" to one output stream. This is useful in the case where we want
// to debug with print statements and want ordered logging (e.g slog.i and slog.e will not appear in
// order of calls).
#define FVK_DEBUG_FORCE_LOG_TO_I 0x00020000
// Useful default combinations
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
#define FVK_DEBUG_PERFORMANCE \
@@ -133,6 +140,18 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
#define FVK_HANDLE_ARENA_SIZE_IN_MB 8
#endif
#if FVK_ENABLED(FVK_DEBUG_FORCE_LOG_TO_I)
#define FVK_LOGI (utils::slog.i)
#define FVK_LOGD FVK_LOGI
#define FVK_LOGE FVK_LOGI
#define FVK_LOGW FVK_LOGI
#else
#define FVK_LOGE (utils::slog.e)
#define FVK_LOGW (utils::slog.w)
#define FVK_LOGD (utils::slog.d)
#define FVK_LOGI (utils::slog.i)
#endif
// All vkCreate* functions take an optional allocator. For now we select the default allocator by
// passing in a null pointer, and we highlight the argument by using the VKALLOC constant.
constexpr struct VkAllocationCallbacks* VKALLOC = nullptr;
@@ -100,7 +100,7 @@ std::tuple<uint32_t, uint32_t> VulkanTimestamps::getNextQuery() {
return std::make_tuple(timerIndex * 2, timerIndex * 2 + 1);
}
}
utils::slog.e << "More than " << maxTimers << " timers are not supported." << utils::io::endl;
FVK_LOGE << "More than " << maxTimers << " timers are not supported." << utils::io::endl;
return std::make_tuple(0, 1);
}
@@ -131,6 +131,10 @@ public:
return mDebugUtilsSupported;
}
inline bool isMultiviewEnabled() const noexcept {
return mMultiviewEnabled;
}
inline bool isClipDistanceSupported() const noexcept {
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
}
@@ -141,6 +145,7 @@ private:
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures = {};
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mMultiviewEnabled = false;
VkFormatList mDepthStencilFormats;
VkFormatList mBlittableDepthStencilFormats;
+21 -21
View File
@@ -115,15 +115,15 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) {
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
utils::slog.e << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
FVK_LOGE << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
} else {
// TODO: emit best practices warnings about aggressive pipeline barriers.
if (strstr(pMessage, "ALL_GRAPHICS_BIT") || strstr(pMessage, "ALL_COMMANDS_BIT")) {
return VK_FALSE;
}
utils::slog.w << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
FVK_LOGW << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
}
utils::slog.e << utils::io::endl;
FVK_LOGE << utils::io::endl;
return VK_FALSE;
}
#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION)
@@ -133,18 +133,18 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
VkDebugUtilsMessageTypeFlagsEXT types, const VkDebugUtilsMessengerCallbackDataEXT* cbdata,
void* pUserData) {
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
utils::slog.e << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
<< utils::io::endl;
FVK_LOGE << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
<< utils::io::endl;
} else {
// TODO: emit best practices warnings about aggressive pipeline barriers.
if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT")
|| strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) {
return VK_FALSE;
}
utils::slog.w << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
<< utils::io::endl;
FVK_LOGW << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
<< utils::io::endl;
}
utils::slog.e << utils::io::endl;
FVK_LOGE << utils::io::endl;
return VK_FALSE;
}
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
@@ -292,7 +292,7 @@ Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& cont
// VulkanRenderTarget : 312 few
// -- less than or equal to 312 bytes
utils::slog.d
FVK_LOGD
<< "\nVulkanSwapChain: " << sizeof(VulkanSwapChain)
<< "\nVulkanBufferObject: " << sizeof(VulkanBufferObject)
<< "\nVulkanVertexBuffer: " << sizeof(VulkanVertexBuffer)
@@ -660,8 +660,8 @@ void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
<< utils::io::endl;
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it"
<< utils::io::endl;
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
@@ -672,7 +672,7 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
uint32_t height, uint64_t flags) {
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it"
<< utils::io::endl;
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
@@ -914,7 +914,7 @@ bool VulkanDriver::isStereoSupported() {
case backend::StereoscopicType::INSTANCED:
return mContext.isClipDistanceSupported();
case backend::StereoscopicType::MULTIVIEW:
// TODO: implement multiview feature in Vulkan.
return mContext.isMultiviewEnabled();
case backend::StereoscopicType::NONE:
return false;
}
@@ -986,15 +986,15 @@ FeatureLevel VulkanDriver::getFeatureLevel() {
// If the max sampler counts do not meet FL2 standards, then this is an FL1 device.
const auto& fl2 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_2];
if (fl2.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
fl2.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
if (limits.maxPerStageDescriptorSamplers < fl2.MAX_VERTEX_SAMPLER_COUNT ||
limits.maxPerStageDescriptorSamplers < fl2.MAX_FRAGMENT_SAMPLER_COUNT) {
return FeatureLevel::FEATURE_LEVEL_1;
}
// If the max sampler counts do not meet FL3 standards, then this is an FL2 device.
const auto& fl3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
if (fl3.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
fl3.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
if (limits.maxPerStageDescriptorSamplers < fl3.MAX_VERTEX_SAMPLER_COUNT||
limits.maxPerStageDescriptorSamplers < fl3.MAX_FRAGMENT_SAMPLER_COUNT) {
return FeatureLevel::FEATURE_LEVEL_2;
}
@@ -1862,9 +1862,9 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
// 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) && FVK_ENABLED_DEBUG_SAMPLER_NAME
utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
utils::slog.w << " in material '" << program->name.c_str() << "'";
utils::slog.w << " at binding point " << +binding << utils::io::endl;
FVK_LOGW << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
FVK_LOGW << " in material '" << program->name.c_str() << "'";
FVK_LOGW << " at binding point " << +binding << utils::io::endl;
#endif
texture = mEmptyTexture;
}
@@ -2014,7 +2014,7 @@ void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, cons
assert_invariant(inRenderPass);
inRenderPass = false;
} else if (inRenderPass && OUTSIDE_COMMANDS.find(command) != OUTSIDE_COMMANDS.end()) {
utils::slog.e << command.data() << " issued inside a render pass." << utils::io::endl;
FVK_LOGE << command.data() << " issued inside a render pass." << utils::io::endl;
}
#endif
}
@@ -95,7 +95,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
}
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
utils::slog.d << "Creating framebuffer " << config.width << "x" << config.height << " "
FVK_LOGD << "Creating framebuffer " << config.width << "x" << config.height << " "
<< "for render pass " << config.renderPass << ", "
<< "samples = " << int(config.samples) << ", "
<< "depth = " << (config.depth ? 1 : 0) << ", "
@@ -310,7 +310,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
mRenderPassCache[config] = {renderPass, mCurrentTime};
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
utils::slog.d << "Created render pass " << renderPass << " with "
FVK_LOGD << "Created render pass " << renderPass << " with "
<< "samples = " << int(config.samples) << ", "
<< "depth = " << (hasDepth ? 1 : 0) << ", "
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
@@ -292,8 +292,8 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
}
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
<< ", " << modules[1] << ")" << utils::io::endl;
FVK_LOGD << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
<< ", " << modules[1] << ")" << utils::io::endl;
#endif
}
@@ -230,14 +230,15 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
PipelineCacheEntry cacheEntry = {};
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
utils::slog.d << "vkCreateGraphicsPipelines with shaders = ("
<< shaderStages[0].module << ", " << shaderStages[1].module << ")" << utils::io::endl;
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
<< shaderStages[0].module << ", " << shaderStages[1].module << ")"
<< utils::io::endl;
#endif
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo,
VKALLOC, &cacheEntry.handle);
assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) {
utils::slog.e << "vkCreateGraphicsPipelines error " << error << utils::io::endl;
FVK_LOGE << "vkCreateGraphicsPipelines error " << error << utils::io::endl;
return nullptr;
}
@@ -252,7 +253,7 @@ void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
if (mPipelineRequirements.shaders[0] == VK_NULL_HANDLE ||
mPipelineRequirements.shaders[1] == VK_NULL_HANDLE) {
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
FVK_LOGE << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
}
#endif
}
@@ -167,9 +167,9 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
#if FVK_ENABLED(FVK_DEBUG_READ_PIXELS)
utils::slog.d << "readPixels created image=" << stagingImage
<< " to copy from image=" << srcTexture->getVkImage()
<< " src-layout=" << srcTexture->getLayout(0, 0) << utils::io::endl;
FVK_LOGD << "readPixels created image=" << stagingImage
<< " to copy from image=" << srcTexture->getVkImage()
<< " src-layout=" << srcTexture->getLayout(0, 0) << utils::io::endl;
#endif
VkMemoryRequirements memReqs;
@@ -185,7 +185,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
if (memoryTypeIndex >= VK_MAX_MEMORY_TYPES) {
memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
utils::slog.w
FVK_LOGW
<< "readPixels is slow because VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not available"
<< utils::io::endl;
}
@@ -303,7 +303,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
VkResult status = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
// Fence hasn't been reached. Try waiting again.
if (status != VK_SUCCESS) {
utils::slog.e << "Failed to wait for readPixels fence" << utils::io::endl;
FVK_LOGE << "Failed to wait for readPixels fence" << utils::io::endl;
return;
}
@@ -321,7 +321,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
getComponentCount(srcFormat), srcPixels,
static_cast<int>(subResourceLayout.rowPitch), static_cast<int>(width),
static_cast<int>(height), swizzle)) {
utils::slog.e << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
FVK_LOGE << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
}
vkUnmapMemory(device, stagingMemory);
@@ -111,11 +111,11 @@ private:
#if DEBUG_RESOURCE_LEAKS
public:
void print() {
utils::slog.d << "Resource Allocator state (debug only)" << utils::io::endl;
FVK_LOGD << "Resource Allocator state (debug only)" << utils::io::endl;
for (size_t i = 0; i < RESOURCE_TYPE_COUNT; i++) {
utils::slog.d << "[" << i << "]=" << mDebugOnlyResourceCount[i] << utils::io::endl;
FVK_LOGD << "[" << i << "]=" << mDebugOnlyResourceCount[i] << utils::io::endl;
}
utils::slog.d << "+++++++++++++++++++++++++++++++++++++" << utils::io::endl;
FVK_LOGD << "+++++++++++++++++++++++++++++++++++++" << utils::io::endl;
}
private:
utils::FixedCapacityVector<size_t> mDebugOnlyResourceCount;
@@ -61,7 +61,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
#if FVK_ENABLED(FVK_DEBUG_ALLOCATION)
if (result != VK_SUCCESS) {
utils::slog.e << "Allocation error: " << result << utils::io::endl;
FVK_LOGE << "Allocation error: " << result << utils::io::endl;
}
#endif
+19 -34
View File
@@ -35,29 +35,13 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
mStagePool(stagePool),
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
mCurrentImageReadyIndex(0),
mTransitionSwapChainImageLayoutForPresent(
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
mAcquired(false),
mIsFirstRenderPass(true) {
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
FILAMENT_CHECK_POSTCONDITION(swapChain) << "Unable to create swapchain";
VkSemaphoreCreateInfo const createInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
// No need to wait on this semaphore before drawing when in Headless mode.
if (mHeadless) {
// Set all sempahores to VK_NULL_HANDLE
memset(mImageReady, 0, sizeof(mImageReady[0]) * IMAGE_READY_SEMAPHORE_COUNT);
} else {
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
VkResult result =
vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, mImageReady + i);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Failed to create semaphore";
}
}
update();
}
@@ -68,11 +52,6 @@ VulkanSwapChain::~VulkanSwapChain() {
mCommands->wait();
mPlatform->destroy(swapChain);
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
if (mImageReady[i] != VK_NULL_HANDLE) {
vkDestroySemaphore(mPlatform->getDevice(), mImageReady[i], VKALLOC);
}
}
}
void VulkanSwapChain::update() {
@@ -95,7 +74,7 @@ void VulkanSwapChain::update() {
}
void VulkanSwapChain::present() {
if (!mHeadless) {
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
VkCommandBuffer const cmdbuf = mCommands->get().buffer();
VkImageSubresourceRange const subresources{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
@@ -106,10 +85,15 @@ void VulkanSwapChain::present() {
};
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
}
mCommands->flush();
// We only present if it is not headless. No-op for headless (but note that we still need the
// flush() in the above line).
mCommands->flush();
// call the image ready wait function
if (mExplicitImageReadyWait != nullptr) {
mExplicitImageReadyWait(swapChain);
}
// We only present if it is not headless. No-op for headless.
if (!mHeadless) {
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing);
@@ -139,13 +123,14 @@ void VulkanSwapChain::acquire(bool& resized) {
update();
}
mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT;
const VkSemaphore imageReady = mImageReady[mCurrentImageReadyIndex];
VkResult const result = mPlatform->acquire(swapChain, imageReady, &mCurrentSwapIndex);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain.";
if (imageReady != VK_NULL_HANDLE) {
mCommands->injectDependency(imageReady);
VulkanPlatform::ImageSyncData imageSyncData;
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
mCurrentSwapIndex = imageSyncData.imageIndex;
mExplicitImageReadyWait = imageSyncData.explicitImageReadyWait;
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain.";
if (imageSyncData.imageReadySemaphore != VK_NULL_HANDLE) {
mCommands->injectDependency(imageSyncData.imageReadySemaphore);
}
mAcquired = true;
}
@@ -50,7 +50,10 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource {
void acquire(bool& reized);
inline VulkanTexture* getCurrentColor() const noexcept {
return mColors[mCurrentSwapIndex].get();
uint32_t const imageIndex = mCurrentSwapIndex;
FILAMENT_CHECK_PRECONDITION(
imageIndex != VulkanPlatform::ImageSyncData::INVALID_IMAGE_INDEX);
return mColors[imageIndex].get();
}
inline VulkanTexture* getDepth() const noexcept {
@@ -80,15 +83,15 @@ private:
VulkanStagePool& mStagePool;
bool const mHeadless;
bool const mFlushAndWaitOnResize;
bool const mTransitionSwapChainImageLayoutForPresent;
// We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout
// transitions, which are useful here.
utils::FixedCapacityVector<std::unique_ptr<VulkanTexture>> mColors;
std::unique_ptr<VulkanTexture> mDepth;
VkExtent2D mExtent;
VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT];
uint32_t mCurrentImageReadyIndex;
uint32_t mCurrentSwapIndex;
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
bool mAcquired;
bool mIsFirstRenderPass;
};
@@ -113,7 +113,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, mVkFormat, &props);
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
utils::slog.w << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not "
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not "
"sampleable with optimal tiling." << utils::io::endl;
}
#endif
@@ -163,7 +163,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
utils::slog.d << "vkCreateImage: "
FVK_LOGD << "vkCreateImage: "
<< "image = " << mTextureImage << ", "
<< "result = " << error << ", "
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
@@ -450,7 +450,7 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres
}
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
utils::slog.d << "transition texture=" << mTextureImage
FVK_LOGD << "transition texture=" << mTextureImage
<< " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount
@@ -539,7 +539,7 @@ void VulkanTexture::print() const {
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
level >= mPrimaryViewRange.baseMipLevel &&
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
utils::slog.d << "[" << mTextureImage << "]: (" << layer << "," << level
FVK_LOGD << "[" << mTextureImage << "]: (" << layer << "," << level
<< ")=" << getLayout(layer, level)
<< " primary=" << primary
<< utils::io::endl;
@@ -548,7 +548,7 @@ void VulkanTexture::print() const {
for (auto view: mCachedImageViews) {
auto& range = view.first.range;
utils::slog.d << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
FVK_LOGD << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type
@@ -33,11 +33,11 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
// build the push constant layout key
uint32_t pushConstantRangeCount = program->getPushConstantRangeCount();
auto const& pushCostantRanges = program->getPushConstantRanges();
auto const& pushConstantRanges = program->getPushConstantRanges();
if (pushConstantRangeCount > 0) {
assert_invariant(pushConstantRangeCount <= Program::SHADER_TYPE_COUNT);
for (uint8_t i = 0; i < pushConstantRangeCount; ++i) {
auto const& range = pushCostantRanges[i];
auto const& range = pushConstantRanges[i];
auto& pushConstant = key.pushConstant[i];
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
pushConstant.stage = static_cast<uint8_t>(ShaderStage::VERTEX);
@@ -52,9 +52,8 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
}
}
auto iter = mPipelineLayouts.find(key);
if (iter != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry = mPipelineLayouts[key];
if (PipelineLayoutMap::iterator iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry = iter.value();
entry.lastUsed = mTimestamp++;
return entry.handle;
}
@@ -65,7 +64,7 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
.setLayoutCount = (uint32_t) descSetLayoutCount,
.pSetLayouts = key.descSetLayouts.data(),
.pushConstantRangeCount = pushConstantRangeCount,
.pPushConstantRanges = pushCostantRanges,
.pPushConstantRanges = pushConstantRanges,
};
VkPipelineLayout layout;
@@ -95,7 +95,7 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
.pNext = &driverProperties,
};
vkGetPhysicalDeviceProperties2(device, &physicalDeviceProperties2);
utils::slog.i << "Vulkan device driver: " << driverProperties.driverName << " "
FVK_LOGI << "Vulkan device driver: " << driverProperties.driverName << " "
<< driverProperties.driverInfo << utils::io::endl;
}
@@ -128,7 +128,7 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
const FixedCapacityVector<VkPhysicalDevice> physicalDevices
= filament::backend::enumerate(vkEnumeratePhysicalDevices, instance);
utils::slog.i << "Selected physical device '" << deviceProperties.deviceName << "' from "
FVK_LOGI << "Selected physical device '" << deviceProperties.deviceName << "' from "
<< physicalDevices.size() << " physical devices. "
<< "(vendor " << utils::io::hex << vendorID << ", "
<< "device " << deviceID << ", "
@@ -142,15 +142,15 @@ void printDepthFormats(VkPhysicalDevice device) {
// Note that Vulkan is more constrained than OpenGL ES 3.1 in this area.
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
utils::slog.i << "Sampleable depth formats: ";
FVK_LOGI << "Sampleable depth formats: ";
for (VkFormat format : ALL_VK_FORMATS) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(device, format, &props);
if ((props.optimalTilingFeatures & required) == required) {
utils::slog.i << format << " ";
FVK_LOGI << format << " ";
}
}
utils::slog.i << utils::io::endl;
FVK_LOGI << utils::io::endl;
}
#endif
@@ -198,6 +198,7 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
VK_KHR_MAINTENANCE2_EXTENSION_NAME,
VK_KHR_MAINTENANCE3_EXTENSION_NAME,
VK_KHR_MULTIVIEW_EXTENSION_NAME,
};
ExtensionSet exts;
// Identify supported physical device extensions
@@ -241,11 +242,11 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
instanceCreateInfo.ppEnabledLayerNames = enabledLayers.data();
} else {
#if defined(__ANDROID__)
utils::slog.d << "Validation layers are not available; did you set jniLibs in your "
<< "gradle file?" << utils::io::endl;
FVK_LOGD << "Validation layers are not available; did you set jniLibs in your "
<< "gradle file?" << utils::io::endl;
#else
utils::slog.d << "Validation layer not available; did you install the Vulkan SDK?\n"
<< "Please ensure that VK_LAYER_PATH is set correctly." << utils::io::endl;
FVK_LOGD << "Validation layer not available; did you install the Vulkan SDK?\n"
<< "Please ensure that VK_LAYER_PATH is set correctly." << utils::io::endl;
#endif // __ANDROID__
}
@@ -334,6 +335,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
void* pNext = nullptr;
VkPhysicalDevicePortabilitySubsetFeaturesKHR portability = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR,
.pNext = nullptr,
@@ -341,9 +343,24 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
.mutableComparisonSamplers = VK_TRUE,
};
if (setContains(deviceExtensions, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
deviceCreateInfo.pNext = &portability;
portability.pNext = pNext;
pNext = &portability;
}
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
.pNext = nullptr,
.multiview = VK_TRUE,
.multiviewGeometryShader = VK_FALSE,
.multiviewTessellationShader = VK_FALSE
};
if (setContains(deviceExtensions, VK_KHR_MULTIVIEW_EXTENSION_NAME)) {
multiview.pNext = pNext;
pNext = &multiview;
}
deviceCreateInfo.pNext = pNext;
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, VKALLOC, &device);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateDevice error=" << result << ".";
@@ -418,8 +435,8 @@ inline int deviceTypeOrder(VkPhysicalDeviceType deviceType) {
case VK_PHYSICAL_DEVICE_TYPE_OTHER:
return 1;
default:
utils::slog.w << "deviceTypeOrder: Unexpected deviceType: " << deviceType
<< utils::io::endl;
FVK_LOGW << "deviceTypeOrder: Unexpected deviceType: " << deviceType
<< utils::io::endl;
return -1;
}
}
@@ -701,6 +718,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
= pruneExtensions(mImpl->mPhysicalDevice, instExts, deviceExts);
instExts = prunedInstExts;
deviceExts = prunedDeviceExts;
if (driverConfig.stereoscopicType != StereoscopicType::MULTIVIEW) {
deviceExts.erase(VK_KHR_MULTIVIEW_EXTENSION_NAME);
}
}
mImpl->mDevice
@@ -716,6 +737,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
// Store the extension support in the context
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
#ifdef NDEBUG
// If we are in release build, we should not have turned on debug extensions
@@ -750,8 +772,8 @@ VulkanPlatform::SwapChainBundle VulkanPlatform::getSwapChainBundle(SwapChainPtr
SWAPCHAIN_RET_FUNC(getSwapChainBundle, handle, )
}
VkResult VulkanPlatform::acquire(SwapChainPtr handle, VkSemaphore clientSignal, uint32_t* index) {
SWAPCHAIN_RET_FUNC(acquire, handle, clientSignal, index)
VkResult VulkanPlatform::acquire(SwapChainPtr handle, ImageSyncData* outImageSyncData) {
SWAPCHAIN_RET_FUNC(acquire, handle, outImageSyncData)
}
VkResult VulkanPlatform::present(SwapChainPtr handle, uint32_t index,
@@ -133,6 +133,7 @@ VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext con
VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
vkDestroySwapchainKHR(mDevice, mSwapchain, VKALLOC);
vkDestroySurfaceKHR(mInstance, mSurface, VKALLOC);
destroy();
}
VkResult VulkanPlatformSurfaceSwapChain::create() {
@@ -153,7 +154,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
// the number of images, though there may be limits related to the total amount of memory used
// by presentable images."
if (maxImageCount != 0 && desiredImageCount > maxImageCount) {
utils::slog.e << "Swap chain does not support " << desiredImageCount << " images."
FVK_LOGE << "Swap chain does not support " << desiredImageCount << " images."
<< utils::io::endl;
desiredImageCount = caps.minImageCount;
}
@@ -246,7 +247,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), mHasStencil);
mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat);
slog.i << "vkCreateSwapchain"
FVK_LOGI << "vkCreateSwapchain"
<< ": " << mSwapChainBundle.extent.width << "x" << mSwapChainBundle.extent.height << ", "
<< surfaceFormat.format << ", " << surfaceFormat.colorSpace << ", "
<< "swapchain-size=" << mSwapChainBundle.colors.size() << ", "
@@ -254,19 +255,29 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
<< "depth=" << mSwapChainBundle.depthFormat
<< io::endl;
VkSemaphoreCreateInfo const semaphoreCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
VkResult result = vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr,
mImageReady + i);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Failed to create semaphore";
}
return result;
}
VkResult VulkanPlatformSurfaceSwapChain::acquire(VkSemaphore clientSignal, uint32_t* index) {
// This immediately retrieves the index of the next available presentable image, and
// asynchronously requests the GPU to trigger the "imageAvailable" semaphore.
VkResult result = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, clientSignal,
VK_NULL_HANDLE, index);
VkResult VulkanPlatformSurfaceSwapChain::acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT;
outImageSyncData->imageReadySemaphore = mImageReady[mCurrentImageReadyIndex];
VkResult result = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX,
outImageSyncData->imageReadySemaphore, VK_NULL_HANDLE, &outImageSyncData->imageIndex);
// Users should be notified of a suboptimal surface, but it should not cause a cascade of
// log messages or a loop of re-creations.
if (result == VK_SUBOPTIMAL_KHR && !mSuboptimal) {
slog.w << "Vulkan Driver: Suboptimal swap chain." << io::endl;
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain." << io::endl;
mSuboptimal = true;
}
return result;
@@ -288,7 +299,7 @@ VkResult VulkanPlatformSurfaceSwapChain::present(uint32_t index, VkSemaphore fin
// On Android Q and above, a suboptimal surface is always reported after screen rotation:
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
if (result == VK_SUBOPTIMAL_KHR && !mSuboptimal) {
utils::slog.w << "Vulkan Driver: Suboptimal swap chain." << utils::io::endl;
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain." << utils::io::endl;
mSuboptimal = true;
}
return result;
@@ -312,6 +323,17 @@ VkResult VulkanPlatformSurfaceSwapChain::recreate() {
return create();
}
void VulkanPlatformSurfaceSwapChain::destroy() {
VulkanPlatformSwapChainImpl::destroy();
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
if (mImageReady[i] != VK_NULL_HANDLE) {
vkDestroySemaphore(mDevice, mImageReady[i], VKALLOC);
mImageReady[i] = VK_NULL_HANDLE;
}
}
}
VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext const& context,
VkDevice device, VkQueue queue, VkExtent2D extent, uint64_t flags)
: VulkanPlatformSwapChainImpl(context, device, queue),
@@ -343,8 +365,8 @@ VkResult VulkanPlatformHeadlessSwapChain::present(uint32_t index, VkSemaphore fi
return VK_SUCCESS;
}
VkResult VulkanPlatformHeadlessSwapChain::acquire(VkSemaphore clientSignal, uint32_t* index) {
*index = mCurrentIndex;
VkResult VulkanPlatformHeadlessSwapChain::acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
outImageSyncData->imageIndex = mCurrentIndex;
mCurrentIndex = (mCurrentIndex + 1) % HEADLESS_SWAPCHAIN_SIZE;
return VK_SUCCESS;
}
@@ -42,7 +42,7 @@ struct VulkanPlatformSwapChainImpl : public Platform::SwapChain {
~VulkanPlatformSwapChainImpl();
// Non-virtual override-able method
VkResult acquire(VkSemaphore clientSignal, uint32_t* index) {
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
PANIC_PRECONDITION("Should not be called");
return VK_ERROR_UNKNOWN;
}
@@ -86,7 +86,7 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
~VulkanPlatformSurfaceSwapChain();
// Non-virtual override
VkResult acquire(VkSemaphore clientSignal, uint32_t* index);
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
// Non-virtual override
VkResult present(uint32_t index, VkSemaphore finished);
@@ -97,7 +97,13 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
// Non-virtual override-able method
bool hasResized();
protected:
// Non-virtual override-able method
void destroy();
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
VkResult create();
VkInstance mInstance;
@@ -106,6 +112,8 @@ private:
VkSurfaceKHR mSurface;
VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
VkExtent2D const mFallbackExtent;
VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT];
uint32_t mCurrentImageReadyIndex;
bool mUsesRGB = false;
bool mHasStencil = false;
@@ -121,7 +129,7 @@ struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainImpl {
~VulkanPlatformHeadlessSwapChain();
// Non-virtual override
VkResult acquire(VkSemaphore clientSignal, uint32_t* index);
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
// Non-virtual override
VkResult present(uint32_t index, VkSemaphore finished);
+5 -4
View File
@@ -64,14 +64,15 @@ First determine the Filament material and variant that causes the problem.
What we want is the "raw" GLSL version of the shader, before any optimizations / cross-compilation
happens.
The easiest way I've found to do this is by adding a `std::cout` statement inside `MaterialBuilder`,
right after `sg.createFragmentProgram` or `sg.createVertexProgram` is called. For example,
We can use the `--save-raw-variants` debug flag in matc to export each GLSL
shader to a file. For example:
```
if (v.variant.key == 8u) { std::cout << shader << std::endl; }
matc --save-raw-variants --optimize-size --variant-filter fog,ssr,vsm,stereo \
-a all -p all -o mymaterial.filamat mymaterial.mat
```
Then, I invoke `matc` and redirect stdout to a .frag or .vert file.
Files will be named like `mymaterial_0x05.frag` or `mymaterial_0x05.vert`.
Note that gltfio material "templates" first go through a build step. After building gltfio, the
gltfio Filament materials are output to:
+38 -2
View File
@@ -298,6 +298,24 @@ public:
*/
size_t textureUseAfterFreePoolSize = 0;
/**
* When uploading vertex or index data, the Filament Metal backend copies data
* into a shared staging area before transferring it to the GPU. This setting controls
* the total size of the buffer used to perform these allocations.
*
* Higher values can improve performance when performing many uploads across a small
* number of frames.
*
* This buffer remains alive throughout the lifetime of the Engine, so this size adds to the
* memory footprint of the app and should be set as conservative as possible.
*
* A value of 0 disables the shared staging buffer entirely; uploads will acquire an
* individual buffer from a pool of shared buffers.
*
* Only respected by the Metal backend.
*/
size_t metalUploadBufferSizeBytes = 512 * 1024;
/**
* Set to `true` to forcibly disable parallel shader compilation in the backend.
* Currently only honored by the GL and Metal backends.
@@ -332,9 +350,12 @@ public:
uint32_t resourceAllocatorCacheSizeMB = 64;
/*
* This value determines for how many frames are texture entries kept in the cache.
* This value determines how many frames texture entries are kept for in the cache. This
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
* Typically only one texture is evicted per frame.
* The default is 1.
*/
uint32_t resourceAllocatorCacheMaxAge = 2;
uint32_t resourceAllocatorCacheMaxAge = 1;
/*
* Disable backend handles use-after-free checks.
@@ -1021,6 +1042,21 @@ public:
void resetBackendState() noexcept;
#endif
/**
* Get the current time. This is a convenience function that simply returns the
* time in nanosecond since epoch of std::chrono::steady_clock.
* A possible implementation is:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* return std::chrono::steady_clock::now().time_since_epoch().count();
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* @return current time in nanosecond since epoch of std::chrono::steady_clock.
* @see Renderer::beginFrame()
*/
static uint64_t getSteadyClockTimeNano() noexcept;
DebugRegistry& getDebugRegistry() noexcept;
protected:
@@ -158,6 +158,8 @@ public:
*
* @return This Builder, for chaining calls.
*
* @see Material::Builder::sphericalHarmonicsBandCount()
*
* @note
* Because the coefficients are pre-scaled, `sh[0]` is the environment's
* average irradiance.
+12
View File
@@ -140,6 +140,18 @@ public:
return constant(name, strlen(name), value);
}
/**
* Sets the quality of the indirect lights computations. This is only taken into account
* if this material is lit and in the surface domain. This setting will affect the
* IndirectLight computation if one is specified on the Scene and Spherical Harmonics
* are used for the irradiance.
*
* @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default).
* @return Reference to this Builder for chaining calls.
* @see IndirectLight
*/
Builder& sphericalHarmonicsBandCount(size_t shBandCount) noexcept;
/**
* Creates the Material object and returns a pointer to it.
*
@@ -140,6 +140,7 @@ public:
* @param values Array of values to set to the named parameter array.
* @param count Size of the array to set.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
* @see Material::hasParameter
*/
template<typename T, typename = is_supported_parameter_t<T>>
void setParameter(const char* UTILS_NONNULL name, size_t nameLength,
@@ -234,6 +235,32 @@ public:
setParameter(name, strlen(name), type, color);
}
/**
* Gets the value of a parameter by name.
*
* Note: Only supports non-texture parameters such as numeric and math types.
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*
* @see Material::hasParameter
*/
template<typename T>
T getParameter(const char* UTILS_NONNULL name, size_t nameLength) const;
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_parameter_t<T>>
inline T getParameter(StringLiteral name) const {
return getParameter<T>(name.data, name.size);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_parameter_t<T>>
inline T getParameter(const char* UTILS_NONNULL name) const {
return getParameter<T>(name, strlen(name));
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
+6 -41
View File
@@ -489,43 +489,20 @@ public:
* For standard morphing, A MorphTargetBuffer must be provided.
* Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets.
*
* For legacy morphing, the attached VertexBuffer must provide data in the
* appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only
* supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must
* be enabled on the material definition: either via the legacyMorphing material attribute
* or by calling filamat::MaterialBuilder::useLegacyMorphing().
*
* See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis
* to advance the animation.
*/
Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept;
/**
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
*/
Builder& morphing(uint8_t level, size_t primitiveIndex,
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer,
size_t offset, size_t count) noexcept;
/**
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
*/
inline Builder& morphing(uint8_t level, size_t primitiveIndex,
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept {
return morphing(level, primitiveIndex, morphTargetBuffer, 0,
morphTargetBuffer->getVertexCount());
}
/**
* Specifies the the range of the MorphTargetBuffer to use with this primitive.
*
* @param level the level of detail (lod), only 0 can be specified
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
*/
Builder& morphing(uint8_t level, size_t primitiveIndex,
size_t offset, size_t count) noexcept;
RenderableManager::Builder& morphing(uint8_t level,
size_t primitiveIndex, size_t offset) noexcept;
/**
@@ -786,25 +763,13 @@ public:
/**
* Associates a MorphTargetBuffer to the given primitive.
*/
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
size_t offset, size_t count);
/** @deprecated */
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count);
/** @deprecated */
inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) {
setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0,
morphTargetBuffer->getVertexCount());
}
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
size_t offset);
/**
* Get a MorphTargetBuffer to the given primitive or null if it doesn't exist.
* Get a MorphTargetBuffer to the given renderable or null if it doesn't exist.
*/
MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBufferAt(Instance instance,
uint8_t level, size_t primitiveIndex) const noexcept;
MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBuffer(Instance instance) const noexcept;
/**
* Gets the number of morphing in the given entity.
+52
View File
@@ -22,9 +22,11 @@
#include <filament/FilamentAPI.h>
#include <utils/compiler.h>
#include <utils/FixedCapacityVector.h>
#include <math/vec4.h>
#include <stddef.h>
#include <stdint.h>
namespace filament {
@@ -81,6 +83,37 @@ public:
UTILS_DEPRECATED uint64_t vsyncOffsetNanos = 0;
};
/**
* Timing information about a frame
* @see getFrameInfoHistory()
*/
struct FrameInfo {
using time_point_ns = int64_t;
using duration_ns = int64_t;
uint32_t frameId; //!< monotonically increasing frame identifier
duration_ns frameTime; //!< frame duration on the GPU in nanosecond [ns]
duration_ns denoisedFrameTime; //!< denoised frame duration on the GPU in [ns]
time_point_ns beginFrame; //!< Renderer::beginFrame() time since epoch [ns]
time_point_ns endFrame; //!< Renderer::endFrame() time since epoch [ns]
time_point_ns backendBeginFrame; //!< Backend thread time of frame start since epoch [ns]
time_point_ns backendEndFrame; //!< Backend thread time of frame end since epoch [ns]
};
/**
* Retrieve an historic of frame timing information. The maximum frame history size is
* given by getMaxFrameHistorySize().
* @param historySize requested history size. The returned vector could be smaller.
* @return A vector of FrameInfo.
*/
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(
size_t historySize = 1) const noexcept;
/**
* @return the maximum supported frame history size.
* @see getFrameInfoHistory()
*/
size_t getMaxFrameHistorySize() const noexcept;
/**
* Use FrameRateOptions to set the desired frame rate and control how quickly the system
* reacts to GPU load changes.
@@ -227,6 +260,25 @@ public:
static constexpr CopyFrameFlag CLEAR = 0x4;
/**
* The use of this method is optional. It sets the VSYNC time expressed as the duration in
* nanosecond since epoch of std::chrono::steady_clock.
* If called, passing 0 to vsyncSteadyClockTimeNano in Renderer::BeginFrame will use this
* time instead.
* @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock
* @see Engine::getSteadyClockTimeNano()
* @see Renderer::BeginFrame()
*/
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
/**
* Call skipFrame when momentarily skipping frames, for instance if the content of the
* scene doesn't change.
*
* @param vsyncSteadyClockTimeNano
*/
void skipFrame(uint64_t vsyncSteadyClockTimeNano = 0u);
/**
* Set-up a frame for this Renderer.
*
+12
View File
@@ -40,6 +40,7 @@ class CallbackHandler;
class Camera;
class ColorGrading;
class Engine;
class MaterialInstance;
class RenderTarget;
class Scene;
@@ -878,6 +879,17 @@ public:
*/
utils::Entity getFogEntity() const noexcept;
/**
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
* keeps a history of previous frame renders associated with the Renderer the view was last
* used with. When switching Renderer, it may be necessary to clear that history by calling
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
* being very different.
*/
void clearFrameHistory(Engine& engine) noexcept;
/**
* List of available ambient occlusion techniques
* @deprecated use AmbientOcclusionOptions::enabled instead
+7 -1
View File
@@ -39,6 +39,8 @@
#include <utils/compiler.h>
#include <utils/Panic.h>
#include <chrono>
#include <stddef.h>
#include <stdint.h>
@@ -361,7 +363,7 @@ const Engine::Config& Engine::getConfig() const noexcept {
return downcast(this)->getConfig();
}
bool Engine::isStereoSupported(StereoscopicType stereoscopicType) const noexcept {
bool Engine::isStereoSupported(StereoscopicType) const noexcept {
return downcast(this)->isStereoSupported();
}
@@ -369,6 +371,10 @@ size_t Engine::getMaxStereoscopicEyes() noexcept {
return FEngine::getMaxStereoscopicEyes();
}
uint64_t Engine::getSteadyClockTimeNano() noexcept {
return std::chrono::steady_clock::now().time_since_epoch().count();
}
#if defined(__EMSCRIPTEN__)
void Engine::resetBackendState() noexcept {
downcast(this)->resetBackendState();
+3
View File
@@ -21,6 +21,9 @@
#include <fg/FrameGraphTexture.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <stdint.h>
namespace filament {
+132 -45
View File
@@ -16,30 +16,33 @@
#include "FrameInfo.h"
#include <filament/Renderer.h>
#include <backend/DriverEnums.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
#include <utils/ostream.h>
#include <math/scalar.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <memory>
#include <ratio>
#include <cmath>
#include <stdint.h>
#include <stddef.h>
namespace filament {
using namespace utils;
using namespace backend;
// this is to avoid a call to memmove
template<class InputIterator, class OutputIterator>
static inline
void move_backward(InputIterator first, InputIterator last, OutputIterator result) {
while (first != last) {
*--result = *--last;
}
}
FrameInfoManager::FrameInfoManager(DriverApi& driver) noexcept {
for (auto& query : mQueries) {
query = driver.createTimerQuery();
query.handle = driver.createTimerQuery();
}
}
@@ -47,14 +50,36 @@ FrameInfoManager::~FrameInfoManager() noexcept = default;
void FrameInfoManager::terminate(DriverApi& driver) noexcept {
for (auto& query : mQueries) {
driver.destroyTimerQuery(query);
driver.destroyTimerQuery(query.handle);
}
}
void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32_t) noexcept {
driver.beginTimerQuery(mQueries[mIndex]);
void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config, uint32_t frameId) noexcept {
auto& history = mFrameTimeHistory;
// don't exceed the capacity, drop the oldest entry
if (UTILS_LIKELY(history.size() == history.capacity())) {
history.pop_back();
}
// create a new entry
auto& front = history.emplace_front(frameId);
// store the current time
front.beginFrame = std::chrono::steady_clock::now();
// references are not invalidated by CircularQueue<>, so we can associate a reference to
// the slot we created to the timer query used to find the frame time.
mQueries[mIndex].pInfo = std::addressof(front);
// issue the timer query
driver.beginTimerQuery(mQueries[mIndex].handle);
// issue the custom backend command to get the backend time
driver.queueCommand([&front](){
front.backendBeginFrame = std::chrono::steady_clock::now();
});
// now is a good time to check the oldest active query
uint64_t elapsed = 0;
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast], &elapsed);
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast].handle, &elapsed);
switch (result) {
case TimerQueryResult::NOT_READY:
// nothing to do
@@ -63,49 +88,111 @@ void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32
mLast = (mLast + 1) % POOL_COUNT;
break;
case TimerQueryResult::AVAILABLE:
mLast = (mLast + 1) % POOL_COUNT;
// conversion to our duration happens here
mFrameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
pFront = mQueries[mLast].pInfo;
pFront->frameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
mLast = (mLast + 1) % POOL_COUNT;
denoiseFrameTime(config);
break;
}
update(config, mFrameTime);
// keep this just for debugging
if constexpr (false) {
using namespace utils;
auto h = getFrameInfoHistory(1);
if (!h.empty()) {
slog.d << frameId << ": "
<< h[0].frameId << " (" << frameId - h[0].frameId << ")"
<< ", Dm=" << h[0].endFrame - h[0].beginFrame
<< ", L =" << h[0].backendBeginFrame - h[0].beginFrame
<< ", Db=" << h[0].backendEndFrame - h[0].backendBeginFrame
<< ", T =" << h[0].frameTime
<< io::endl;
}
}
}
void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
driver.endTimerQuery(mQueries[mIndex]);
auto& front = mFrameTimeHistory.front();
// close the timer query
driver.endTimerQuery(mQueries[mIndex].handle);
// queue custom backend command to query the current time
driver.queueCommand([&front](){
// backend frame end-time
front.backendEndFrame = std::chrono::steady_clock::now();
// signal that the data is available
front.ready.store(true, std::memory_order_release);
});
// and finally acquire the time on the main thread
front.endFrame = std::chrono::steady_clock::now();
mIndex = (mIndex + 1) % POOL_COUNT;
}
void FrameInfoManager::update(Config const& config,
FrameInfoManager::duration lastFrameTime) noexcept {
// keep an history of frame times
void FrameInfoManager::denoiseFrameTime(Config const& config) noexcept {
auto& history = mFrameTimeHistory;
assert_invariant(!history.empty());
// this is like doing { pop_back(); push_front(); }
filament::move_backward(history.begin(), history.end() - 1, history.end());
history[0].frameTime = lastFrameTime;
mFrameTimeHistorySize = std::min(++mFrameTimeHistorySize, uint32_t(MAX_FRAMETIME_HISTORY));
if (UTILS_UNLIKELY(mFrameTimeHistorySize < 3)) {
// not enough history to do anything useful
history[0].valid = false;
return;
// find the first slot that has a valid frame duration
size_t first = history.size();
for (size_t i = 0, c = history.size(); i < c; ++i) {
if (history[i].frameTime != duration(0)) {
assert_invariant(std::addressof(history[i]) == pFront);
first = i;
break;
}
}
assert_invariant(first != history.size());
// apply a median filter to get a good representation of the frame time of the last
// N frames.
std::array<duration, MAX_FRAMETIME_HISTORY> median; // NOLINT -- it's initialized below
size_t const size = std::min(mFrameTimeHistorySize,
std::min(config.historySize, (uint32_t)median.size()));
for (size_t i = 0; i < size; ++i) {
median[i] = history[i].frameTime;
}
std::sort(median.begin(), median.begin() + size);
duration const denoisedFrameTime = median[size / 2];
// we need at least 3 valid frame time to calculate the median
if (history.size() >= first + 3) {
// apply a median filter to get a good representation of the frame time of the last
// N frames.
std::array<duration, MAX_FRAMETIME_HISTORY> median; // NOLINT -- it's initialized below
size_t const size = std::min({
history.size() - first,
median.size(),
size_t(config.historySize) });
history[0].denoisedFrameTime = denoisedFrameTime;
history[0].valid = true;
for (size_t i = 0; i < size; ++i) {
median[i] = history[first + i].frameTime;
}
std::sort(median.begin(), median.begin() + size);
duration const denoisedFrameTime = median[size / 2];
history[first].denoisedFrameTime = denoisedFrameTime;
history[first].valid = true;
}
}
utils::FixedCapacityVector<Renderer::FrameInfo> FrameInfoManager::getFrameInfoHistory(
size_t historySize) const noexcept {
auto result = utils::FixedCapacityVector<Renderer::FrameInfo>::with_capacity(MAX_FRAMETIME_HISTORY);
auto const& history = mFrameTimeHistory;
size_t i = 0;
size_t const c = history.size();
for (; i < c; ++i) {
auto const& entry = history[i];
if (entry.ready.load(std::memory_order_acquire) && entry.valid) {
// once we found an entry ready,
// we know by construction that all following ones are too
break;
}
}
for (; i < c && historySize; ++i, --historySize) {
auto const& entry = history[i];
using namespace std::chrono;
result.push_back({
entry.frameId,
duration_cast<nanoseconds>(entry.frameTime).count(),
duration_cast<nanoseconds>(entry.denoisedFrameTime).count(),
duration_cast<nanoseconds>(entry.beginFrame.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.endFrame.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.backendBeginFrame.time_since_epoch()).count(),
duration_cast<nanoseconds>(entry.backendEndFrame.time_since_epoch()).count()
});
}
return result;
}
} // namespace filament
+124 -17
View File
@@ -17,30 +17,136 @@
#ifndef TNT_FILAMENT_FRAMEINFO_H
#define TNT_FILAMENT_FRAMEINFO_H
#include "backend/Handle.h"
#include <filament/Renderer.h>
#include <backend/Handle.h>
#include <private/backend/DriverApi.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <array>
#include <atomic>
#include <chrono>
#include <ratio>
#include <type_traits>
#include <stdint.h>
#include <stddef.h>
namespace filament {
class FEngine;
namespace details {
struct FrameInfo {
using duration = std::chrono::duration<float, std::milli>;
duration frameTime{}; // frame period
duration denoisedFrameTime{}; // frame period (median filter)
bool valid = false;
bool valid = false; // true if the data of the structure is valid
};
} // namespace details
struct FrameInfoImpl : public details::FrameInfo {
using clock = std::chrono::steady_clock;
using time_point = clock::time_point;
uint32_t const frameId;
time_point beginFrame; // main thread beginFrame time
time_point endFrame; // main thread endFrame time
time_point backendBeginFrame; // backend thread beginFrame time (makeCurrent time)
time_point backendEndFrame; // backend thread endFrame time (present time)
std::atomic_bool ready{}; // true once backend thread has populated its data
explicit FrameInfoImpl(uint32_t frameId) noexcept
: frameId(frameId) {
}
};
template<typename T, size_t CAPACITY>
class CircularQueue {
public:
using value_type = T;
using reference = value_type&;
using const_reference = value_type const&;
size_t capacity() const {
return CAPACITY;
}
size_t size() const {
return mSize;
}
bool empty() const noexcept {
return !size();
}
void pop_back() noexcept {
assert_invariant(!empty());
--mSize;
std::destroy_at(&mStorage[(mFront - mSize) % CAPACITY]);
}
void push_front(T const& v) noexcept {
assert_invariant(size() < CAPACITY);
mFront = advance(mFront);
new(&mStorage[mFront]) T(v);
++mSize;
}
void push_front(T&& v) noexcept {
assert_invariant(size() < CAPACITY);
mFront = advance(mFront);
new(&mStorage[mFront]) T(std::move(v));
++mSize;
}
template<typename ...Args>
T& emplace_front(Args&&... args) noexcept {
assert_invariant(size() < CAPACITY);
mFront = advance(mFront);
new(&mStorage[mFront]) T(std::forward<Args>(args)...);
++mSize;
return front();
}
T& operator[](size_t pos) noexcept {
assert_invariant(pos < size());
size_t const index = (mFront + CAPACITY - pos) % CAPACITY;
return *std::launder(reinterpret_cast<T*>(&mStorage[index]));
}
T const& operator[](size_t pos) const noexcept {
return const_cast<CircularQueue&>(*this)[pos];
}
T const& front() const noexcept {
assert_invariant(!empty());
return operator[](0);
}
T& front() noexcept {
assert_invariant(!empty());
return operator[](0);
}
private:
using Storage = std::aligned_storage_t<sizeof(T), alignof(T)>;
Storage mStorage[CAPACITY];
uint32_t mFront = 0; // always index 0
uint32_t mSize = 0;
[[nodiscard]] inline uint32_t advance(uint32_t v) noexcept {
return (v + 1) % CAPACITY;
}
};
class FrameInfoManager {
static constexpr size_t POOL_COUNT = 4;
static constexpr size_t MAX_FRAMETIME_HISTORY = 31u;
static constexpr size_t MAX_FRAMETIME_HISTORY = 16u;
public:
using duration = FrameInfo::duration;
using duration = FrameInfoImpl::duration;
using clock = FrameInfoImpl::clock;
struct Config {
uint32_t historySize;
@@ -57,23 +163,24 @@ public:
// call this immediately before "swap buffers"
void endFrame(backend::DriverApi& driver) noexcept;
FrameInfo const& getLastFrameInfo() const noexcept {
return mFrameTimeHistory[0];
details::FrameInfo const& getLastFrameInfo() const noexcept {
// if pFront is not set yet, return front() but in this case front().valid will be false
return pFront ? *pFront : mFrameTimeHistory.front();
}
duration getLastFrameTime() const noexcept {
return getLastFrameInfo().frameTime;
}
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const noexcept;
private:
void update(Config const& config, duration lastFrameTime) noexcept;
backend::Handle<backend::HwTimerQuery> mQueries[POOL_COUNT];
duration mFrameTime{};
uint32_t mIndex = 0;
uint32_t mLast = 0;
std::array<FrameInfo, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
uint32_t mFrameTimeHistorySize = 0;
void denoiseFrameTime(Config const& config) noexcept;
struct Query {
backend::Handle<backend::HwTimerQuery> handle{};
FrameInfoImpl* pInfo = nullptr;
};
std::array<Query, POOL_COUNT> mQueries;
uint32_t mIndex = 0; // index of current query
uint32_t mLast = 0; // index of oldest query still active
FrameInfoImpl* pFront = nullptr; // the most recent slot with a valid frame time
CircularQueue<FrameInfoImpl, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
};
+1
View File
@@ -26,6 +26,7 @@
#include <filament/Viewport.h>
#include <utils/BinaryTreeArray.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
#include <utils/debug.h>
+29
View File
@@ -183,6 +183,35 @@ template UTILS_PUBLIC void MaterialInstance::setParameter<mat4f> (const char*
// ------------------------------------------------------------------------------------------------
template<typename T>
T FMaterialInstance::getParameterImpl(std::string_view name) const {
ssize_t offset = mMaterial->getUniformInterfaceBlock().getFieldOffset(name, 0);
assert_invariant(offset>=0);
return downcast(this)->getUniformBuffer().getUniform<T>(offset);
}
template<typename T>
T MaterialInstance::getParameter(const char* name, size_t nameLength) const {
return downcast(this)->getParameterImpl<T>({ name, nameLength });
}
// explicit template instantiation of our supported types
template UTILS_PUBLIC float MaterialInstance::getParameter<float> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC int32_t MaterialInstance::getParameter<int32_t> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC uint32_t MaterialInstance::getParameter<uint32_t>(const char* name, size_t nameLength) const;
template UTILS_PUBLIC int2 MaterialInstance::getParameter<int2> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC int3 MaterialInstance::getParameter<int3> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC int4 MaterialInstance::getParameter<int4> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC uint2 MaterialInstance::getParameter<uint2> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC uint3 MaterialInstance::getParameter<uint3> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC uint4 MaterialInstance::getParameter<uint4> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC float2 MaterialInstance::getParameter<float2> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC float3 MaterialInstance::getParameter<float3> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC float4 MaterialInstance::getParameter<float4> (const char* name, size_t nameLength) const;
template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const char* name, size_t nameLength) const;
// ------------------------------------------------------------------------------------------------
Material const* MaterialInstance::getMaterial() const noexcept {
return downcast(this)->getMaterial();
}
+4 -2
View File
@@ -1965,8 +1965,8 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
// - visible bloom size changes with dynamic resolution in non-homogenous mode
// This allows us to use the 9 sample downsampling filter (instead of 13)
// for at least 4 levels.
uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
uint32_t width = std::max(16u, uint32_t(std::floor(bloomWidth)));
uint32_t height = std::max(16u, uint32_t(std::floor(bloomHeight)));
width &= ~((1 << 4) - 1); // at least 4 levels
height &= ~((1 << 4) - 1);
bloomWidth = float(width);
@@ -1978,6 +1978,8 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
// we don't need to do the fireflies reduction if we have TAA (it already does it)
bool fireflies = threshold && !taaOptions.enabled;
assert_invariant(bloomWidth && bloomHeight);
while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) {
if (inoutBloomOptions.quality == QualityLevel::LOW ||
inoutBloomOptions.quality == QualityLevel::MEDIUM) {
+28 -11
View File
@@ -38,6 +38,7 @@
#include <backend/PipelineState.h>
#include "private/backend/CircularBuffer.h"
#include "private/backend/CommandStream.h"
#include <utils/compiler.h>
#include <utils/debug.h>
@@ -153,7 +154,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
sortCommands(builder.mArena);
if (engine.isAutomaticInstancingEnabled()) {
uint32_t stereoscopicEyeCount = 1;
int32_t stereoscopicEyeCount = 1;
if (builder.mFlags & IS_INSTANCED_STEREOSCOPIC) {
stereoscopicEyeCount *= engine.getConfig().stereoscopicEyeCount;
}
@@ -162,8 +163,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
}
// this destructor is actually heavy because it inlines ~vector<>
RenderPass::~RenderPass() noexcept {
}
RenderPass::~RenderPass() noexcept = default;
void RenderPass::resize(Arena& arena, size_t count) noexcept {
if (mCommandBegin) {
@@ -221,7 +221,7 @@ void RenderPass::appendCommands(FEngine& engine,
work(vr.first, vr.size());
} else {
auto* jobCommandsParallel = jobs::parallel_for(js, nullptr, vr.first, (uint32_t)vr.size(),
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 5>());
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT>());
js.runAndWait(jobCommandsParallel);
}
@@ -671,7 +671,6 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
*/
for (size_t pi = 0, c = primitives.size(); pi < c; ++pi) {
auto const& primitive = primitives[pi];
auto const& morphTargets = morphing.targets[pi];
FMaterialInstance const* const mi = primitive.getMaterialInstance();
FMaterial const* const ma = mi->getMaterial();
@@ -684,8 +683,9 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
cmd.info.indexOffset = primitive.getIndexOffset();
cmd.info.indexCount = primitive.getIndexCount();
cmd.info.type = primitive.getPrimitiveType();
cmd.info.morphTargetBuffer = morphTargets.buffer->getHwHandle();
cmd.info.morphingOffset = morphTargets.offset;
cmd.info.morphTargetBuffer = morphing.morphTargetBuffer ?
morphing.morphTargetBuffer->getHwHandle() : SamplerGroupHandle{};
cmd.info.morphingOffset = primitive.getMorphingBufferOffset();
if constexpr (isColorPass) {
RenderPass::setupColorCommand(cmd, renderableVariant, mi, inverseFrontFaces);
@@ -885,14 +885,31 @@ void RenderPass::Executor::execute(FEngine& engine,
// Maximum space occupied in the CircularBuffer by a single `Command`. This must be
// reevaluated when the inner loop below adds DriverApi commands or when we change the
// CommandStream protocol. Currently, the maximum is 240 bytes, and we use 256 to be on
// the safer side.
size_t const maxCommandSizeInBytes = 256;
// CommandStream protocol. Currently, the maximum is 320 bytes.
// The batch size is calculated by adding the size of all commands that can possibly be
// emitted per draw call:
constexpr size_t const maxCommandSizeInBytes =
sizeof(CustomCommand) +
sizeof(COMMAND_TYPE(scissor)) +
sizeof(COMMAND_TYPE(bindUniformBuffer)) +
sizeof(COMMAND_TYPE(bindSamplers)) +
sizeof(COMMAND_TYPE(bindBufferRange)) +
sizeof(COMMAND_TYPE(bindBufferRange)) +
sizeof(COMMAND_TYPE(bindSamplers)) +
sizeof(COMMAND_TYPE(bindSamplers)) +
sizeof(COMMAND_TYPE(bindUniformBuffer)) +
sizeof(COMMAND_TYPE(bindSamplers)) +
sizeof(COMMAND_TYPE(bindSamplers)) +
sizeof(COMMAND_TYPE(bindPipeline)) +
sizeof(COMMAND_TYPE(setPushConstant)) +
sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
sizeof(COMMAND_TYPE(draw2));
// Number of Commands that can be issued and guaranteed to fit in the current
// CircularBuffer allocation. In practice, we'll have tons of headroom especially if
// skinning and morphing aren't used. With a 2 MiB buffer (the default) a batch is
// 8192 commands (i.e. draw calls).
// 6553 commands (i.e. draw calls).
size_t const batchCommandCount = capacity / maxCommandSizeInBytes;
while(first != last) {
Command const* const batchLast = std::min(first + batchCommandCount, last);
+1 -2
View File
@@ -417,8 +417,7 @@ private:
void instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) noexcept;
// We choose the command count per job to minimize JobSystem overhead.
// On a Pixel 4, 2048 commands is about half a millisecond of processing.
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 2048;
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 128;
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_SIZE =
sizeof(Command) * JOBS_PARALLEL_FOR_COMMANDS_COUNT;
+6
View File
@@ -56,6 +56,7 @@ public:
backend::VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; }
uint32_t getIndexOffset() const noexcept { return mIndexOffset; }
uint32_t getIndexCount() const noexcept { return mIndexCount; }
uint32_t getMorphingBufferOffset() const noexcept { return mMorphingBufferOffset; }
backend::PrimitiveType getPrimitiveType() const noexcept { return mPrimitiveType; }
AttributeBitset getEnabledAttributes() const noexcept { return mEnabledAttributes; }
@@ -72,6 +73,10 @@ public:
mGlobalBlendOrderEnabled = enabled;
}
void setMorphingBufferOffset(uint32_t offset) noexcept {
mMorphingBufferOffset = offset;
}
private:
// These first fields are dereferences from PrimitiveInfo, keep them together
struct {
@@ -80,6 +85,7 @@ private:
backend::Handle<backend::HwVertexBufferInfo> mVertexBufferInfoHandle = {};
uint32_t mIndexOffset = 0;
uint32_t mIndexCount = 0;
uint32_t mMorphingBufferOffset = 0;
};
AttributeBitset mEnabledAttributes = {};
+6 -12
View File
@@ -158,20 +158,14 @@ void RenderableManager::setMorphWeights(Instance instance, float const* weights,
downcast(this)->setMorphWeights(instance, weights, count, offset);
}
void RenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) {
downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex,
downcast(morphTargetBuffer), offset, count);
void RenderableManager::setMorphTargetBufferOffsetAt(Instance instance, uint8_t level,
size_t primitiveIndex,
size_t offset) {
downcast(this)->setMorphTargetBufferOffsetAt(instance, level, primitiveIndex, offset);
}
void RenderableManager::setMorphTargetBufferAt(
Instance instance, uint8_t level, size_t primitiveIndex, size_t offset, size_t count) {
downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex, offset, count);
}
MorphTargetBuffer* RenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level,
size_t primitiveIndex) const noexcept {
return downcast(this)->getMorphTargetBufferAt(instance, level, primitiveIndex);
MorphTargetBuffer* RenderableManager::getMorphTargetBuffer(Instance instance) const noexcept {
return downcast(this)->getMorphTargetBuffer(instance);
}
size_t RenderableManager::getMorphTargetCount(Instance instance) const noexcept {
+25
View File
@@ -14,11 +14,20 @@
* limitations under the License.
*/
#include <filament/Renderer.h>
#include "details/Renderer.h"
#include "details/Engine.h"
#include "details/View.h"
#include <utils/FixedCapacityVector.h>
#include <utility>
#include <stddef.h>
#include <stdint.h>
namespace filament {
using namespace math;
@@ -36,6 +45,10 @@ void Renderer::setPresentationTime(int64_t monotonic_clock_ns) {
downcast(this)->setPresentationTime(monotonic_clock_ns);
}
void Renderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
downcast(this)->skipFrame(vsyncSteadyClockTimeNano);
}
bool Renderer::beginFrame(SwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
return downcast(this)->beginFrame(downcast(swapChain), vsyncSteadyClockTimeNano);
}
@@ -89,4 +102,16 @@ void Renderer::renderStandaloneView(View const* view) {
downcast(this)->renderStandaloneView(downcast(view));
}
void Renderer::setVsyncTime(uint64_t steadyClockTimeNano) noexcept {
downcast(this)->setVsyncTime(steadyClockTimeNano);
}
utils::FixedCapacityVector<Renderer::FrameInfo> Renderer::getFrameInfoHistory(size_t historySize) const noexcept {
return downcast(this)->getFrameInfoHistory(historySize);
}
size_t Renderer::getMaxFrameHistorySize() const noexcept {
return downcast(this)->getMaxFrameHistorySize();
}
} // namespace filament
+1 -1
View File
@@ -172,7 +172,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
if (engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
if (view.hasStereo() && engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
layerCount = engine.getConfig().stereoscopicEyeCount;
}
+131 -32
View File
@@ -27,15 +27,18 @@
#include "private/backend/DriverApi.h"
#include <utils/algorithm.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <array>
#include <algorithm>
#include <iterator>
#include <memory>
#include <optional>
#include <utility>
#include <stddef.h>
@@ -89,8 +92,15 @@ void ResourceAllocator::AssociativeContainer<K, V, H>::emplace(ARGS&& ... args)
}
// ------------------------------------------------------------------------------------------------
ResourceAllocatorInterface::~ResourceAllocatorInterface() = default;
// ------------------------------------------------------------------------------------------------
ResourceAllocatorDisposerInterface::~ResourceAllocatorDisposerInterface() = default;
// ------------------------------------------------------------------------------------------------
size_t ResourceAllocator::TextureKey::getSize() const noexcept {
size_t const pixelCount = width * height * depth;
size_t size = pixelCount * FTexture::getFormatSize(format);
@@ -110,16 +120,22 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept {
ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
mBackend(driverApi) {
mBackend(driverApi),
mDisposer(std::make_shared<ResourceAllocatorDisposer>(driverApi)) {
}
ResourceAllocator::ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
Engine::Config const& config, DriverApi& driverApi) noexcept
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
mBackend(driverApi),
mDisposer(std::move(disposer)) {
}
ResourceAllocator::~ResourceAllocator() noexcept {
assert_invariant(!mTextureCache.size());
assert_invariant(!mInUseTextures.size());
assert_invariant(mTextureCache.empty());
}
void ResourceAllocator::terminate() noexcept {
assert_invariant(!mInUseTextures.size());
auto& textureCache = mTextureCache;
for (auto it = textureCache.begin(); it != textureCache.end();) {
mBackend.destroyTexture(it->second.handle);
@@ -174,7 +190,7 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
swizzle[0], swizzle[1], swizzle[2], swizzle[3]);
}
}
mInUseTextures.emplace(handle, key);
mDisposer->checkout(handle, key);
} else {
if (swizzle == defaultSwizzle) {
handle = mBackend.createTexture(
@@ -190,58 +206,99 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
void ResourceAllocator::destroyTexture(TextureHandle h) noexcept {
if constexpr (mEnabled) {
// find the texture in the in-use list (it must be there!)
auto it = mInUseTextures.find(h);
assert_invariant(it != mInUseTextures.end());
// move it to the cache
const TextureKey key = it->second;
uint32_t const size = key.getSize();
mTextureCache.emplace(key, TextureCachePayload{ h, mAge, size });
mCacheSize += size;
// remove it from the in-use list
mInUseTextures.erase(it);
auto const key = mDisposer->checkin(h);
if (UTILS_LIKELY(key.has_value())) {
uint32_t const size = key.value().getSize();
mTextureCache.emplace(key.value(), TextureCachePayload{ h, mAge, size });
mCacheSize += size;
mCacheSizeHiWaterMark = std::max(mCacheSizeHiWaterMark, mCacheSize);
}
} else {
mBackend.destroyTexture(h);
}
}
void ResourceAllocator::gc() noexcept {
// this is called regularly -- usually once per frame of each Renderer
ResourceAllocatorDisposerInterface& ResourceAllocator::getDisposer() noexcept {
return *mDisposer;
}
// increase our age
const size_t age = mAge++;
void ResourceAllocator::gc(bool skippedFrame) noexcept {
// this is called regularly -- usually once per frame
// increase our age at each (non-skipped) frame
const size_t age = mAge;
if (!skippedFrame) {
mAge++;
}
// Purging strategy:
// - remove entries that are older than a certain age
// - remove only one entry per gc(),
// - remove all entries older than MAX_AGE_SKIPPED_FRAME when skipping a frame
// - remove entries older than mCacheMaxAgeSoft
// - remove only MAX_EVICTION_COUNT entry per gc(),
// - look for the number of unique resource ages present in the cache (this basically gives
// us how many buckets of resources we have corresponding to previous frames.
// - remove all resources that have an age older than the MAX_UNIQUE_AGE_COUNT'th bucket
auto& textureCache = mTextureCache;
// when skipping a frame, the maximum age to keep in the cache
constexpr size_t MAX_AGE_SKIPPED_FRAME = 1;
// maximum entry count to evict per GC, under the mCacheMaxAgeSoft limit
constexpr size_t MAX_EVICTION_COUNT = 1;
// maximum number of unique ages in the cache
constexpr size_t MAX_UNIQUE_AGE_COUNT = 3;
utils::bitset32 ages;
uint32_t evictedCount = 0;
for (auto it = textureCache.begin(); it != textureCache.end();) {
const size_t ageDiff = age - it->second.age;
if (ageDiff >= mCacheMaxAge) {
size_t const ageDiff = age - it->second.age;
if ((ageDiff >= MAX_AGE_SKIPPED_FRAME && skippedFrame) ||
(ageDiff >= mCacheMaxAge && evictedCount < MAX_EVICTION_COUNT)) {
evictedCount++;
purge(it);
// only purge a single entry per gc
break;
} else {
// build the set of ages present in the cache after eviction
ages.set(std::min(size_t(31), ageDiff));
++it;
}
}
// if we have MAX_UNIQUE_AGE_COUNT ages or more, we evict all the resources that
// are older than the MAX_UNIQUE_AGE_COUNT'th age.
if (!skippedFrame && ages.count() >= MAX_UNIQUE_AGE_COUNT) {
uint32_t bits = ages.getValue();
// remove from the set the ages we keep
for (size_t i = 0; i < MAX_UNIQUE_AGE_COUNT - 1; i++) {
bits &= ~(1 << utils::ctz(bits));
}
size_t const maxAge = utils::ctz(bits);
for (auto it = textureCache.begin(); it != textureCache.end();) {
const size_t ageDiff = age - it->second.age;
if (ageDiff >= maxAge) {
purge(it);
} else {
++it;
}
}
}
}
UTILS_NOINLINE
void ResourceAllocator::dump(bool brief) const noexcept {
slog.d << "# entries=" << mTextureCache.size() << ", sz=" << mCacheSize / float(1u << 20u)
<< " MiB" << io::endl;
constexpr float MiB = 1.0f / float(1u << 20u);
slog.d << "# entries=" << mTextureCache.size()
<< ", sz=" << (float)mCacheSize * MiB << " MiB"
<< ", max=" << (float)mCacheSizeHiWaterMark * MiB << " MiB"
<< io::endl;
if (!brief) {
for (auto const& it : mTextureCache) {
auto w = it.first.width;
auto h = it.first.height;
auto f = FTexture::getFormatSize(it.first.format);
slog.d << it.first.name << ": w=" << w << ", h=" << h << ", f=" << f << ", sz="
<< it.second.size / float(1u << 20u) << io::endl;
<< (float)it.second.size * MiB << io::endl;
}
}
}
@@ -254,4 +311,46 @@ void ResourceAllocator::purge(
mTextureCache.erase(pos);
}
// ------------------------------------------------------------------------------------------------
ResourceAllocatorDisposer::ResourceAllocatorDisposer(DriverApi& driverApi) noexcept
: mBackend(driverApi) {
}
ResourceAllocatorDisposer::~ResourceAllocatorDisposer() noexcept {
assert_invariant(mInUseTextures.empty());
}
void ResourceAllocatorDisposer::terminate() noexcept {
assert_invariant(mInUseTextures.empty());
}
void ResourceAllocatorDisposer::destroy(backend::TextureHandle handle) noexcept {
if (handle) {
auto r = checkin(handle);
if (r.has_value()) {
mBackend.destroyTexture(handle);
}
}
}
void ResourceAllocatorDisposer::checkout(backend::TextureHandle handle,
ResourceAllocator::TextureKey key) {
mInUseTextures.emplace(handle, key);
}
std::optional<ResourceAllocator::TextureKey> ResourceAllocatorDisposer::checkin(
backend::TextureHandle handle) {
// find the texture in the in-use list (it must be there!)
auto it = mInUseTextures.find(handle);
assert_invariant(it != mInUseTextures.end());
if (it == mInUseTextures.end()) {
return std::nullopt;
}
TextureKey const key = it->second;
// remove it from the in-use list
mInUseTextures.erase(it);
return key;
}
} // namespace filament
+46 -5
View File
@@ -28,16 +28,29 @@
#include <utils/Hash.h>
#include <array>
#include <vector>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include <cstddef>
#include <stddef.h>
#include <stdint.h>
namespace filament {
class ResourceAllocatorDisposer;
// The only reason we use an interface here is for unit-tests, so we can mock this allocator.
// This is not too time-critical, so that's okay.
class ResourceAllocatorDisposerInterface {
public:
virtual void destroy(backend::TextureHandle handle) noexcept = 0;
protected:
virtual ~ResourceAllocatorDisposerInterface();
};
class ResourceAllocatorInterface {
public:
virtual backend::RenderTargetHandle createRenderTarget(const char* name,
@@ -60,15 +73,20 @@ public:
virtual void destroyTexture(backend::TextureHandle h) noexcept = 0;
virtual ResourceAllocatorDisposerInterface& getDisposer() noexcept = 0;
protected:
virtual ~ResourceAllocatorInterface();
};
class ResourceAllocator final : public ResourceAllocatorInterface {
public:
explicit ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
explicit ResourceAllocator(
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
~ResourceAllocator() noexcept override;
void terminate() noexcept;
@@ -93,7 +111,9 @@ public:
void destroyTexture(backend::TextureHandle h) noexcept override;
void gc() noexcept;
ResourceAllocatorDisposerInterface& getDisposer() noexcept override;
void gc(bool skippedFrame = false) noexcept;
private:
size_t const mCacheMaxAge;
@@ -181,6 +201,7 @@ private:
using value_type = typename Container::value_type::second_type;
size_t size() const { return mContainer.size(); }
bool empty() const { return size() == 0; }
iterator begin() { return mContainer.begin(); }
const_iterator begin() const { return mContainer.begin(); }
iterator end() { return mContainer.end(); }
@@ -193,16 +214,36 @@ private:
};
using CacheContainer = AssociativeContainer<TextureKey, TextureCachePayload>;
using InUseContainer = AssociativeContainer<backend::TextureHandle, TextureKey>;
void purge(ResourceAllocator::CacheContainer::iterator const& pos);
backend::DriverApi& mBackend;
std::shared_ptr<ResourceAllocatorDisposer> mDisposer;
CacheContainer mTextureCache;
InUseContainer mInUseTextures;
size_t mAge = 0;
uint32_t mCacheSize = 0;
uint32_t mCacheSizeHiWaterMark = 0;
static constexpr bool mEnabled = true;
friend class ResourceAllocatorDisposer;
};
class ResourceAllocatorDisposer final : public ResourceAllocatorDisposerInterface {
using TextureKey = ResourceAllocator::TextureKey;
public:
explicit ResourceAllocatorDisposer(backend::DriverApi& driverApi) noexcept;
~ResourceAllocatorDisposer() noexcept override;
void terminate() noexcept;
void destroy(backend::TextureHandle handle) noexcept override;
private:
friend class ResourceAllocator;
void checkout(backend::TextureHandle handle, TextureKey key);
std::optional<TextureKey> checkin(backend::TextureHandle handle);
using InUseContainer = ResourceAllocator::AssociativeContainer<backend::TextureHandle, TextureKey>;
backend::DriverApi& mBackend;
InUseContainer mInUseTextures;
};
} // namespace filament
+6
View File
@@ -15,6 +15,8 @@
*/
#include "details/View.h"
#include "filament/View.h"
namespace filament {
@@ -312,4 +314,8 @@ utils::Entity View::getFogEntity() const noexcept {
return downcast(this)->getFogEntity();
}
void View::clearFrameHistory(Engine& engine) noexcept {
downcast(this)->clearFrameHistory(downcast(engine));
}
} // namespace filament
+5 -4
View File
@@ -20,12 +20,13 @@
#include "details/Engine.h"
#include <math/fast.h>
#include <math/scalar.h>
#include <utils/debug.h>
#include <filament/LightManager.h>
#include <utils/debug.h>
#include <utils/Log.h>
#include <math/fast.h>
#include <math/scalar.h>
using namespace filament::math;
using namespace utils;
+22 -62
View File
@@ -281,19 +281,13 @@ RenderableManager::Builder& RenderableManager::Builder::morphing(
return *this;
}
RenderableManager::Builder& RenderableManager::Builder::morphing(
uint8_t level, size_t primitiveIndex, size_t offset, size_t count) noexcept {
return morphing(level, primitiveIndex, mImpl->mMorphTargetBuffer, offset, count);
}
RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t, size_t primitiveIndex,
MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept {
std::vector<Entry>& entries = mImpl->mEntries;
RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t level,
size_t primitiveIndex, size_t offset) noexcept {
// the last parameter "count" is unused, because it must be equal to the primitive's vertex count
std::vector<BuilderDetails::Entry>& entries = mImpl->mEntries;
if (primitiveIndex < entries.size()) {
auto& morphing = entries[primitiveIndex].morphing;
morphing.buffer = morphTargetBuffer;
morphing.offset = offset;
morphing.count = count;
morphing.offset = uint32_t(offset);
}
return *this;
}
@@ -449,10 +443,10 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
<< ") instances when supplying transforms via an InstanceBuffer.";
if (mImpl->mGeometryType == GeometryType::STATIC) {
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBoneCount > 0)
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBoneCount == 0)
<< "Skinning can't be used with STATIC geometry";
FILAMENT_CHECK_PRECONDITION(mImpl->mMorphTargetCount > 0)
FILAMENT_CHECK_PRECONDITION(mImpl->mMorphTargetCount == 0)
<< "Morphing can't be used with STATIC geometry";
}
@@ -668,13 +662,6 @@ void FRenderableManager::create(
if (morphTargetBuffer == nullptr) {
morphTargetBuffer = mEngine.getDummyMorphTargetBuffer();
}
MorphTargets* const morphTargets = new MorphTargets[entryCount];
std::generate_n(morphTargets, entryCount,
[morphTargetBuffer]() -> MorphTargets {
return { morphTargetBuffer, 0, 0 };
});
mManager[ci].morphTargets = { morphTargets, size_type(entryCount) };
// Always create skinning and morphing resources if one of them is enabled because
// the shader always handles both. See Variant::SKINNING_OR_MORPHING.
@@ -700,13 +687,13 @@ void FRenderableManager::create(
backend::BufferUsage::DYNAMIC),
.count = targetCount };
for (size_t i = 0; i < entryCount; ++i) {
const auto& morphing = builder->mEntries[i].morphing;
if (!morphing.buffer) {
continue;
Slice<FRenderPrimitive>& primitives = mManager[ci].primitives;
mManager[ci].morphTargetBuffer = morphTargetBuffer;
if (builder->mMorphTargetBuffer) {
for (size_t i = 0; i < entryCount; ++i) {
const auto& morphing = builder->mEntries[i].morphing;
primitives[i].setMorphingBufferOffset(morphing.offset);
}
morphTargets[i] = { downcast(morphing.buffer), (uint32_t)morphing.offset,
(uint32_t)morphing.count };
}
// When targetCount equal 0, boneCount>0 in this case, do an initialization for the
@@ -762,7 +749,6 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept {
// See create(RenderableManager::Builder&, Entity)
destroyComponentPrimitives(mHwRenderPrimitiveFactory, driver, manager[ci].primitives);
destroyComponentMorphTargets(engine, manager[ci].morphTargets);
// destroy the bones structures if any
Bones const& bones = manager[ci].bones;
@@ -795,11 +781,6 @@ void FRenderableManager::destroyComponentPrimitives(
delete[] primitives.data();
}
void FRenderableManager::destroyComponentMorphTargets(FEngine&,
utils::Slice<MorphTargets>& morphTargets) noexcept {
delete[] morphTargets.data();
}
void FRenderableManager::setMaterialInstanceAt(Instance instance, uint8_t level,
size_t primitiveIndex, FMaterialInstance const* mi) {
if (instance) {
@@ -968,42 +949,21 @@ void FRenderableManager::setMorphWeights(Instance instance, float const* weights
}
}
void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level,
size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) {
void FRenderableManager::setMorphTargetBufferOffsetAt(Instance instance, uint8_t level,
size_t primitiveIndex,
size_t offset) {
if (instance) {
assert_invariant(morphTargetBuffer);
MorphWeights const& morphWeights = mManager[instance].morphWeights;
FILAMENT_CHECK_PRECONDITION(morphWeights.count == count)
<< "Only " << morphWeights.count << " morph targets can be set (count=" << count
<< ")";
Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
if (primitiveIndex < morphTargets.size()) {
morphTargets[primitiveIndex] = { morphTargetBuffer, (uint32_t)offset,
(uint32_t)count };
assert_invariant(mManager[instance].morphTargetBuffer);
Slice<FRenderPrimitive>& primitives = mManager[instance].primitives;
if (primitiveIndex < primitives.size()) {
primitives[primitiveIndex].setMorphingBufferOffset(offset);
}
}
}
void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level,
size_t primitiveIndex, size_t offset, size_t count) {
MorphTargetBuffer* FRenderableManager::getMorphTargetBuffer(Instance instance) const noexcept {
if (instance) {
Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
if (primitiveIndex < morphTargets.size()) {
setMorphTargetBufferAt(instance, level,
primitiveIndex, morphTargets[primitiveIndex].buffer, offset, count);
}
}
}
MorphTargetBuffer* FRenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level,
size_t primitiveIndex) const noexcept {
if (instance) {
const Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
if (primitiveIndex < morphTargets.size()) {
return morphTargets[primitiveIndex].buffer;
}
return mManager[instance].morphTargetBuffer;
}
return nullptr;
}
+9 -31
View File
@@ -80,12 +80,6 @@ public:
static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits");
struct MorphTargets {
FMorphTargetBuffer* buffer = nullptr;
uint32_t offset = 0;
uint32_t count = 0;
};
explicit FRenderableManager(FEngine& engine) noexcept;
~FRenderableManager();
@@ -155,11 +149,9 @@ public:
inline void setMorphing(Instance instance, bool enable);
void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset);
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count);
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
size_t offset, size_t count);
MorphTargetBuffer* getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
size_t offset);
MorphTargetBuffer* getMorphTargetBuffer(Instance instance) const noexcept;
size_t getMorphTargetCount(Instance instance) const noexcept;
void setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept;
@@ -189,7 +181,7 @@ public:
struct MorphingBindingInfo {
backend::Handle<backend::HwBufferObject> handle;
uint32_t count;
MorphTargets const* targets; // Pointer to Slice<MorphTargets> at a renderable.
FMorphTargetBuffer const* morphTargetBuffer;
};
inline MorphingBindingInfo getMorphingBufferInfo(Instance instance) const noexcept;
@@ -218,16 +210,12 @@ public:
AttributeBitset getEnabledAttributesAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
inline utils::Slice<FRenderPrimitive> const& getRenderPrimitives(Instance instance, uint8_t level) const noexcept;
inline utils::Slice<FRenderPrimitive>& getRenderPrimitives(Instance instance, uint8_t level) noexcept;
inline utils::Slice<MorphTargets> const& getMorphTargets(Instance instance, uint8_t level) const noexcept;
inline utils::Slice<MorphTargets>& getMorphTargets(Instance instance, uint8_t level) noexcept;
private:
void destroyComponent(Instance ci) noexcept;
static void destroyComponentPrimitives(
HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
utils::Slice<FRenderPrimitive>& primitives) noexcept;
static void destroyComponentMorphTargets(FEngine& engine,
utils::Slice<MorphTargets>& morphTargets) noexcept;
struct Bones {
backend::Handle<backend::HwBufferObject> handle;
@@ -254,7 +242,7 @@ private:
VISIBILITY, // user data
PRIMITIVES, // user data
BONES, // filament data, UBO storing a pointer to the bones information
MORPH_TARGETS
MORPHTARGET_BUFFER // morphtarget buffer for the component
};
using Base = utils::SingleInstanceComponentManager<
@@ -266,7 +254,7 @@ private:
Visibility, // VISIBILITY
utils::Slice<FRenderPrimitive>, // PRIMITIVES
Bones, // BONES
utils::Slice<MorphTargets> // MORPH_TARGETS
FMorphTargetBuffer* // MORPHTARGET_BUFFER
>;
struct Sim : public Base {
@@ -289,7 +277,7 @@ private:
Field<VISIBILITY> visibility;
Field<PRIMITIVES> primitives;
Field<BONES> bones;
Field<MORPH_TARGETS> morphTargets;
Field<MORPHTARGET_BUFFER> morphTargetBuffer;
};
};
@@ -461,8 +449,8 @@ inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexce
FRenderableManager::MorphingBindingInfo
FRenderableManager::getMorphingBufferInfo(Instance instance) const noexcept {
MorphWeights const& morphWeights = mManager[instance].morphWeights;
utils::Slice<MorphTargets> const& morphTargets = getMorphTargets(instance, 0);
return { morphWeights.handle, morphWeights.count, morphTargets.data() };
FMorphTargetBuffer const* const buffer = mManager[instance].morphTargetBuffer;
return { morphWeights.handle, morphWeights.count, buffer };
}
FRenderableManager::InstancesInfo
@@ -480,16 +468,6 @@ utils::Slice<FRenderPrimitive>& FRenderableManager::getRenderPrimitives(
return mManager[instance].primitives;
}
utils::Slice<FRenderableManager::MorphTargets> const& FRenderableManager::getMorphTargets(
Instance instance, UTILS_UNUSED uint8_t level) const noexcept {
return mManager[instance].morphTargets;
}
utils::Slice<FRenderableManager::MorphTargets>& FRenderableManager::getMorphTargets(
Instance instance, UTILS_UNUSED uint8_t level) noexcept {
return mManager[instance].morphTargets;
}
} // namespace filament
#endif // TNT_FILAMENT_COMPONENTS_RENDERABLEMANAGER_H
+10 -6
View File
@@ -102,6 +102,7 @@ Engine* FEngine::create(Engine::Builder const& builder) {
DriverConfig const driverConfig{
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.textureUseAfterFreePoolSize = instance->getConfig().textureUseAfterFreePoolSize,
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile,
.disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck,
.forceGLES2Context = instance->getConfig().forceGLES2Context,
@@ -162,8 +163,7 @@ FEngine* FEngine::getEngine(void* token) {
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(instance->mMainThreadId))
<< "Engine::createAsync() and Engine::getEngine() must be called on the same thread.";
// we use mResourceAllocator as a proxy for "am I already initialized"
if (!instance->mResourceAllocator) {
if (!instance->mInitialized) {
if (UTILS_UNLIKELY(!instance->mDriver)) {
// something went horribly wrong during driver initialization
instance->mDriverThread.join();
@@ -261,7 +261,7 @@ void FEngine::init() {
slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl;
mResourceAllocator = new ResourceAllocator(mConfig, driverApi);
mResourceAllocatorDisposer = std::make_shared<ResourceAllocatorDisposer>(driverApi);
mFullScreenTriangleVb = downcast(VertexBuffer::Builder()
.vertexCount(3)
@@ -424,11 +424,13 @@ void FEngine::init() {
}
});
});
mInitialized = true;
}
FEngine::~FEngine() noexcept {
SYSTRACE_CALL();
delete mResourceAllocator;
assert_invariant(!mResourceAllocatorDisposer);
delete mDriver;
if (mOwnPlatform) {
PlatformFactory::destroy(&mPlatform);
@@ -439,7 +441,7 @@ void FEngine::shutdown() {
SYSTRACE_CALL();
// by construction this should never be nullptr
assert_invariant(mResourceAllocator);
assert_invariant(mResourceAllocatorDisposer);
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(mMainThreadId))
<< "Engine::shutdown() called from the wrong thread!";
@@ -459,7 +461,8 @@ void FEngine::shutdown() {
*/
mPostProcessManager.terminate(driver); // free-up post-process manager resources
mResourceAllocator->terminate();
mResourceAllocatorDisposer->terminate();
mResourceAllocatorDisposer.reset();
mDFG.terminate(*this); // free-up the DFG
mRenderableManager.terminate(); // free-up all renderables
mLightManager.terminate(); // free-up all lights
@@ -678,6 +681,7 @@ int FEngine::loop() {
DriverConfig const driverConfig {
.handleArenaSize = getRequestedDriverHandleArenaSize(),
.textureUseAfterFreePoolSize = mConfig.textureUseAfterFreePoolSize,
.metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes,
.disableParallelShaderCompile = mConfig.disableParallelShaderCompile,
.disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck,
.forceGLES2Context = mConfig.forceGLES2Context,
+11 -4
View File
@@ -86,6 +86,7 @@ namespace filament {
class Renderer;
class MaterialParser;
class ResourceAllocatorDisposer;
namespace backend {
class Driver;
@@ -254,9 +255,13 @@ public:
}
}
ResourceAllocator& getResourceAllocator() noexcept {
assert_invariant(mResourceAllocator);
return *mResourceAllocator;
ResourceAllocatorDisposer& getResourceAllocatorDisposer() noexcept {
assert_invariant(mResourceAllocatorDisposer);
return *mResourceAllocatorDisposer;
}
std::shared_ptr<ResourceAllocatorDisposer> const& getSharedResourceAllocatorDisposer() noexcept {
return mResourceAllocatorDisposer;
}
void* streamAlloc(size_t size, size_t alignment) noexcept;
@@ -490,7 +495,7 @@ private:
FTransformManager mTransformManager;
FLightManager mLightManager;
FCameraManager mCameraManager;
ResourceAllocator* mResourceAllocator = nullptr;
std::shared_ptr<ResourceAllocatorDisposer> mResourceAllocatorDisposer;
HwVertexBufferInfoFactory mHwVertexBufferInfoFactory;
ResourceList<FBufferObject> mBufferObjects{ "BufferObject" };
@@ -562,6 +567,8 @@ private:
std::thread::id mMainThreadId{};
bool mInitialized = false;
// Creation parameters
Config mConfig;
+8
View File
@@ -119,6 +119,7 @@ struct Material::BuilderDetails {
const void* mPayload = nullptr;
size_t mSize = 0;
bool mDefaultMaterial = false;
int32_t mShBandsCount = 3;
std::unordered_map<
utils::CString,
std::variant<int32_t, float, bool>,
@@ -143,6 +144,11 @@ Material::Builder& Material::Builder::package(const void* payload, size_t size)
return *this;
}
Material::Builder& Material::Builder::sphericalHarmonicsBandCount(size_t shBandCount) noexcept {
mImpl->mShBandsCount = math::clamp(shBandCount, size_t(1), size_t(3));
return *this;
}
template<typename T, typename>
Material::Builder& Material::Builder::constant(const char* name, size_t nameLength, T value) {
FILAMENT_CHECK_PRECONDITION(name != nullptr) << "name cannot be null";
@@ -897,6 +903,8 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builde
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT,
(int)engine.getConfig().stereoscopicEyeCount });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_SH_BANDS_COUNT, builder->mShBandsCount });
if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
// The actual value of this spec-constant is set in the OpenGLDriver backend.
mSpecializationConstants.push_back({
+3
View File
@@ -231,6 +231,9 @@ private:
void setParameterImpl(std::string_view name,
FTexture const* texture, TextureSampler const& sampler);
template<typename T>
T getParameterImpl(std::string_view name) const;
// initialize the default instance
FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept;
+12 -6
View File
@@ -29,7 +29,8 @@
#include <utils/Allocator.h>
#include <vector>
#include <stddef.h>
#include <stdint.h>
namespace filament {
@@ -59,16 +60,21 @@ public:
inline size_t getVertexCount() const noexcept { return mVertexCount; }
inline size_t getCount() const noexcept { return mCount; }
private:
friend class FView;
friend class RenderPass;
backend::TextureHandle getPositionsHandle() const noexcept {
return mPbHandle;
}
backend::TextureHandle getTangentsHandle() const noexcept {
return mTbHandle;
}
inline backend::Handle<backend::HwSamplerGroup> getHwHandle() const noexcept { return mSbHandle; }
private:
void updateDataAt(backend::DriverApi& driver, backend::Handle <backend::HwTexture> handle,
backend::PixelDataFormat format, backend::PixelDataType type, const char* out,
size_t elementSize, size_t targetIndex, size_t count, size_t offset);
inline backend::Handle<backend::HwSamplerGroup> getHwHandle() const noexcept { return mSbHandle; }
backend::Handle<backend::HwSamplerGroup> mSbHandle;
backend::Handle<backend::HwTexture> mPbHandle;
backend::Handle<backend::HwTexture> mTbHandle;
+54 -3
View File
@@ -60,6 +60,7 @@
#include <chrono>
#include <limits>
#include <memory>
#include <utility>
#include <stddef.h>
@@ -88,7 +89,11 @@ FRenderer::FRenderer(FEngine& engine) :
mHdrQualityMedium(TextureFormat::R11F_G11F_B10F),
mHdrQualityHigh(TextureFormat::RGB16F),
mIsRGB8Supported(false),
mUserEpoch(engine.getEngineEpoch())
mUserEpoch(engine.getEngineEpoch()),
mResourceAllocator(std::make_unique<ResourceAllocator>(
engine.getSharedResourceAllocatorDisposer(),
engine.getConfig(),
engine.getDriverApi()))
{
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
debugRegistry.registerProperty("d.renderer.doFrameCapture",
@@ -176,6 +181,7 @@ void FRenderer::terminate(FEngine& engine) {
}
mFrameInfoManager.terminate(driver);
mFrameSkipper.terminate(driver);
mResourceAllocator->terminate();
}
void FRenderer::resetUserTime() {
@@ -232,6 +238,43 @@ void FRenderer::setPresentationTime(int64_t monotonic_clock_ns) {
driver.setPresentationTime(monotonic_clock_ns);
}
void FRenderer::setVsyncTime(uint64_t steadyClockTimeNano) noexcept {
mVsyncSteadyClockTimeNano = steadyClockTimeNano;
}
void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
SYSTRACE_CALL();
FILAMENT_CHECK_PRECONDITION(!mSwapChain) <<
"skipFrame() can't be called between beginFrame() and endFrame()";
if (!vsyncSteadyClockTimeNano) {
vsyncSteadyClockTimeNano = mVsyncSteadyClockTimeNano;
mVsyncSteadyClockTimeNano = 0;
}
FEngine& engine = mEngine;
FEngine::DriverApi& driver = engine.getDriverApi();
// Gives the backend a chance to execute periodic tasks. This must be called before
// the frame skipper.
driver.tick();
// do this before engine.flush()
mResourceAllocator->gc(true);
// Run the component managers' GC in parallel
// WARNING: while doing this we can't access any component manager
auto& js = engine.getJobSystem();
auto *job = js.runAndRetain(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
engine.flush(); // flush command stream
// make sure we're done with the gcs
js.waitAndRelease(job);
}
bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
assert_invariant(swapChain);
@@ -252,6 +295,11 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
}
#endif
if (!vsyncSteadyClockTimeNano) {
vsyncSteadyClockTimeNano = mVsyncSteadyClockTimeNano;
mVsyncSteadyClockTimeNano = 0;
}
// get the timestamp as soon as possible
using namespace std::chrono;
const steady_clock::time_point now{ steady_clock::now() };
@@ -371,7 +419,7 @@ void FRenderer::endFrame() {
}
// do this before engine.flush()
engine.getResourceAllocator().gc();
mResourceAllocator->gc();
// Run the component managers' GC in parallel
// WARNING: while doing this we can't access any component manager
@@ -459,6 +507,9 @@ void FRenderer::renderStandaloneView(FView const* view) {
FILAMENT_CHECK_PRECONDITION(view->getRenderTarget())
<< "View \"" << view->getName() << "\" must have a RenderTarget associated";
FILAMENT_CHECK_PRECONDITION(!mSwapChain)
<< "renderStandaloneView() must be called outside of beginFrame() / endFrame()";
if (UTILS_LIKELY(view->getScene())) {
mPreviousRenderTargets.clear();
mFrameId++;
@@ -738,7 +789,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
* Frame graph
*/
FrameGraph fg(engine.getResourceAllocator(),
FrameGraph fg(*mResourceAllocator,
isProtectedContent ? FrameGraph::Mode::PROTECTED : FrameGraph::Mode::UNPROTECTED);
auto& blackboard = fg.getBlackboard();
+29 -3
View File
@@ -29,9 +29,6 @@
#include "backend/DriverApiForward.h"
#include <fg/FrameGraphId.h>
#include <fg/FrameGraphTexture.h>
#include <filament/Renderer.h>
#include <filament/Viewport.h>
@@ -40,11 +37,25 @@
#include <utils/compiler.h>
#include <utils/Allocator.h>
#include <utils/FixedCapacityVector.h>
#include <math/vec4.h>
#include <tsl/robin_set.h>
#include <algorithm>
#include <chrono>
#include <functional>
#include <memory>
#include <utility>
#include <stddef.h>
#include <stdint.h>
namespace filament {
class ResourceAllocator;
namespace backend {
class Driver;
} // namespace backend
@@ -78,6 +89,11 @@ public:
void setPresentationTime(int64_t monotonic_clock_ns);
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
// skip a frame
void skipFrame(uint64_t vsyncSteadyClockTimeNano);
// start a frame
bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano);
@@ -131,6 +147,14 @@ public:
return mClearOptions;
}
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const noexcept {
return mFrameInfoManager.getFrameInfoHistory(historySize);
}
size_t getMaxFrameHistorySize() const noexcept {
return MAX_FRAMETIME_HISTORY;
}
private:
friend class Renderer;
using Command = RenderPass::Command;
@@ -187,6 +211,8 @@ private:
backend::TargetBufferFlags mClearFlags{};
tsl::robin_set<FRenderTarget*> mPreviousRenderTargets;
std::function<void()> mBeginFrameInternal;
uint64_t mVsyncSteadyClockTimeNano = 0;
std::unique_ptr<ResourceAllocator> mResourceAllocator{};
};
FILAMENT_DOWNCAST(Renderer)
+1 -1
View File
@@ -248,7 +248,7 @@ void FScene::prepare(utils::JobSystem& js,
auto* renderableJob = jobs::parallel_for(js, rootJob,
renderableInstances.data(), renderableInstances.size(),
std::cref(renderableWork), jobs::CountSplitter<128, 5>());
std::cref(renderableWork), jobs::CountSplitter<64>());
auto* lightJob = jobs::parallel_for(js, rootJob,
lightInstances.data(), lightInstances.size(),
+17 -7
View File
@@ -17,6 +17,7 @@
#include "details/View.h"
#include "Culler.h"
#include "FrameHistory.h"
#include "Froxelizer.h"
#include "RenderPrimitive.h"
#include "ResourceAllocator.h"
@@ -118,7 +119,7 @@ void FView::terminate(FEngine& engine) {
DriverApi& driver = engine.getDriverApi();
driver.destroyBufferObject(mLightUbh);
driver.destroyBufferObject(mRenderableUbh);
drainFrameHistory(engine);
clearFrameHistory(engine);
ShadowMapManager::terminate(engine, mShadowMapManager);
mPerViewUniforms.terminate(driver);
@@ -162,7 +163,7 @@ void FView::setDynamicLightingOptions(float zLightNear, float zLightFar) noexcep
}
float2 FView::updateScale(FEngine& engine,
FrameInfo const& info,
filament::details::FrameInfo const& info,
Renderer::FrameRateOptions const& frameRateOptions,
Renderer::DisplayInfo const& displayInfo) noexcept {
@@ -1007,20 +1008,29 @@ FrameGraphId<FrameGraphTexture> FView::renderShadowMaps(FEngine& engine, FrameGr
void FView::commitFrameHistory(FEngine& engine) noexcept {
// Here we need to destroy resources in mFrameHistory.back()
auto& disposer = engine.getResourceAllocatorDisposer();
auto& frameHistory = mFrameHistory;
FrameHistoryEntry& last = frameHistory.back();
last.taa.color.destroy(engine.getResourceAllocator());
last.ssr.color.destroy(engine.getResourceAllocator());
disposer.destroy(last.taa.color.handle);
disposer.destroy(last.ssr.color.handle);
last.taa.color.handle.clear();
last.ssr.color.handle.clear();
// and then push the new history entry to the history stack
frameHistory.commit();
}
void FView::drainFrameHistory(FEngine& engine) noexcept {
void FView::clearFrameHistory(FEngine& engine) noexcept {
// make sure we free all resources in the history
for (size_t i = 0; i < mFrameHistory.size(); ++i) {
commitFrameHistory(engine);
auto& disposer = engine.getResourceAllocatorDisposer();
auto& frameHistory = mFrameHistory;
for (size_t i = 0; i < frameHistory.size(); ++i) {
FrameHistoryEntry& last = frameHistory[i];
disposer.destroy(last.taa.color.handle);
disposer.destroy(last.ssr.color.handle);
last.taa.color.handle.clear();
last.ssr.color.handle.clear();
}
}
+5 -5
View File
@@ -283,7 +283,7 @@ public:
}
math::float2 updateScale(FEngine& engine,
FrameInfo const& info,
details::FrameInfo const& info,
Renderer::FrameRateOptions const& frameRateOptions,
Renderer::DisplayInfo const& displayInfo) noexcept;
@@ -422,6 +422,10 @@ public:
// (e.g.: after the FrameGraph execution).
void commitFrameHistory(FEngine& engine) noexcept;
// Clean-up the whole history, free all resources. This is typically called when the View is
// being terminated. Or we're changing Renderer.
void clearFrameHistory(FEngine& engine) noexcept;
// create the picking query
View::PickingQuery& pick(uint32_t x, uint32_t y, backend::CallbackHandler* handler,
View::PickingQueryResultCallback callback) noexcept;
@@ -485,10 +489,6 @@ private:
Culler::result_type* visibleMask,
size_t count);
// Clean-up the whole history, free all resources. This is typically called when the View is
// being terminated.
void drainFrameHistory(FEngine& engine) noexcept;
// we don't inline this one, because the function is quite large and there is not much to
// gain from inlining.
static FScene::RenderableSoa::iterator partition(
+1
View File
@@ -24,6 +24,7 @@
#include "FrameGraphPass.h"
#include "FrameGraphRenderPass.h"
#include "FrameGraphTexture.h"
#include "ResourceAllocator.h"
#include "details/Engine.h"
+1
View File
@@ -62,6 +62,7 @@ if (ANDROID)
target_link_libraries(test_compiler PRIVATE utils)
target_link_libraries(test_compiler PRIVATE EGL)
target_link_libraries(test_compiler PRIVATE GLESv3)
target_link_libraries(test_compiler PRIVATE bluevk)
endif()
add_executable(test_material_parser
+67 -166
View File
@@ -1,181 +1,82 @@
/*
* 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 <iostream>
#include <utils/Hash.h>
#include <tsl/robin_map.h>
#include <bluevk/BlueVK.h>
#include <utils/Log.h>
namespace {
#include <GLES3/gl3.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <gtest/gtest.h>
using namespace utils;
using namespace std::literals;
class CompilerTest : public testing::Test {
protected:
void SetUp() override {
EGLBoolean success;
EGLint major, minor;
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
ASSERT_NE(dpy, EGL_NO_DISPLAY);
EGLBoolean const initialized = eglInitialize(dpy, &major, &minor);
ASSERT_TRUE(initialized);
EGLint const contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
EGLint configsCount;
EGLint configAttribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, // 0
EGL_RED_SIZE, 8, // 2
EGL_GREEN_SIZE, 8, // 4
EGL_BLUE_SIZE, 8, // 6
EGL_NONE // 14
};
success = eglChooseConfig(dpy, configAttribs, &config, 1, &configsCount);
ASSERT_TRUE(success);
context = eglCreateContext(dpy, config, EGL_NO_CONTEXT, contextAttribs);
ASSERT_NE(context, EGL_NO_CONTEXT);
success = eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
ASSERT_TRUE(success);
ASSERT_EQ(eglGetError(), EGL_SUCCESS);
}
void TearDown() override {
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(dpy, context);
eglTerminate(dpy);
}
private:
EGLDisplay dpy;
EGLContext context;
EGLConfig config;
struct T {
};
TEST_F(CompilerTest, Simple) {
auto shader = R"(
#version 300 es
void main()
{
})"sv;
constexpr uint8_t const UNIQUE_DESCRIPTOR_SET_COUNT = 3;
constexpr uint8_t const SHADER_TYPE_COUNT = 3;
using Timestamp = uint64_t;
using VulkanResourceAllocator = T;
const char* const src = shader.data();
GLint const len = (GLint)shader.size();
struct PushConstantKey {
uint8_t stage;// We have one set of push constant per shader stage (fragment, vertex, etc).
uint8_t size;
};
GLuint const id = glCreateShader(GL_VERTEX_SHADER);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
struct PipelineLayoutKey {
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout, UNIQUE_DESCRIPTOR_SET_COUNT>;
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 3
PushConstantKey pushConstant[SHADER_TYPE_COUNT] = {};// 2 * 3
uint16_t padding = 0;
};
static_assert(sizeof(PipelineLayoutKey) == 32);
glShaderSource(id, 1, &src, &len);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
using PipelineLayoutKeyHashFn = utils::hash::MurmurHashFn<PipelineLayoutKey>;
struct PipelineLayoutKeyEqual {
bool operator()(PipelineLayoutKey const& k1, PipelineLayoutKey const& k2) const {
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(PipelineLayoutKey));
}
};
glCompileShader(id);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
struct PipelineLayoutCacheEntry {
VkPipelineLayout handle;
Timestamp lastUsed;
};
GLint result = 0;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
EXPECT_EQ(result, GL_TRUE);
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
glDeleteShader(id);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
class TestClass {
public:
VkDevice mDevice;
VulkanResourceAllocator* mAllocator;
Timestamp mTimestamp;
PipelineLayoutMap layout;
};
void insert(PipelineLayoutMap& layout, uint64_t val) {
PipelineLayoutKey key{};
key.descSetLayouts[0] = (VkDescriptorSetLayout) val;
layout[key] = {(VkPipelineLayout) val, val};
}
TEST_F(CompilerTest, CrashPVRUniFlexCompileToHw) {
// Some PowerVR driver crash with this shader
auto shader = R"(
#version 300 es
layout(location = 0) in vec4 mesh_position;
layout(std140) uniform FrameUniforms {
vec2 i;
} frameUniforms;
void main() {
gl_Position = mesh_position;
gl_Position.z = dot(gl_Position.zw, frameUniforms.i);
})"sv;
const char* const src = shader.data();
GLint const len = (GLint)shader.size();
GLuint const id = glCreateShader(GL_VERTEX_SHADER);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
glShaderSource(id, 1, &src, &len);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
glCompileShader(id);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
GLint result = 0;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
EXPECT_EQ(result, GL_TRUE);
glDeleteShader(id);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
}
TEST_F(CompilerTest, ConstParameters) {
// Some PowerVR driver fail to compile this shader
auto shader = R"(
#version 300 es
highp mat3 m;
void buggy(const mediump vec3 n) {
m*n;
}
void main() {
})"sv;
const char* const src = shader.data();
GLint const len = (GLint)shader.size();
GLuint const id = glCreateShader(GL_FRAGMENT_SHADER);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
glShaderSource(id, 1, &src, &len);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
glCompileShader(id);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
GLint result = 0;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
EXPECT_EQ(result, GL_TRUE);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
glDeleteShader(id);
EXPECT_EQ(glGetError(), GL_NO_ERROR);
}
}// namespace
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
constexpr uint32_t START = 1000;
TestClass c;
auto& layout = c.layout;
uint64_t count = 0;
for (; count < 20; count++) {
insert(layout, count + START);
}
for (uint64_t t = 0; t < count; ++t) {
PipelineLayoutKey key = {};
key.descSetLayouts[0] = (VkDescriptorSetLayout)(START + t);
if (layout.find(key) != layout.end()) {
std::cout << "found " << t << std::endl;
} else {
std::cout << "not found" << t << std::endl;
}
}
return 0;
}
@@ -34,6 +34,10 @@ using namespace backend;
class MockResourceAllocator : public ResourceAllocatorInterface {
uint32_t handle = 0;
struct MockDisposer : public ResourceAllocatorDisposerInterface {
void destroy(backend::TextureHandle) noexcept override {}
} disposer;
public:
backend::RenderTargetHandle createRenderTarget(const char* name,
backend::TargetBufferFlags targetBufferFlags,
@@ -60,6 +64,10 @@ public:
void destroyTexture(backend::TextureHandle h) noexcept override {
}
ResourceAllocatorDisposerInterface& getDisposer() noexcept override {
return disposer;
}
};
class FrameGraphTest : public testing::Test {
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.53.0"
spec.version = "1.53.2"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.0/filament-v1.53.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.2/filament-v1.53.2-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -109,6 +109,7 @@ public:
vec4 groundPlane;
RayCallback raycastCallback;
void* raycastUserdata;
bool panning = true;
};
struct Builder {
@@ -143,6 +144,8 @@ public:
Builder& groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d); //! Plane equation used as a raycast fallback
Builder& raycastCallback(RayCallback cb, void* userdata); //! Raycast function for accurate grab-and-pan
Builder& panning(bool enabled); //! Sets whether panning is enabled
/**
* Creates a new camera manipulator, either ORBIT, MAP, or FREE_FLIGHT.
*
+8
View File
@@ -144,6 +144,14 @@ Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::raycastCallback(RayCal
return *this;
}
template <typename FLOAT> typename
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::panning(bool enabled) {
details.panning = enabled;
return *this;
}
template <typename FLOAT>
Manipulator<FLOAT>* Manipulator<FLOAT>::Builder::build(Mode mode) {
switch (mode) {
+1 -1
View File
@@ -69,7 +69,7 @@ public:
}
void grabBegin(int x, int y, bool strafe) override {
mGrabState = strafe ? PANNING : ORBITING;
mGrabState = strafe && Base::mProps.panning ? PANNING : ORBITING;
mGrabPivot = mPivot;
mGrabEye = Base::mEye;
mGrabTarget = Base::mTarget;

Some files were not shown because too many files have changed in this diff Show More