Compare commits

..
Author SHA1 Message Date
Doris Wu 56050de5cd metal: implement memory mapping (#9454) 2025-11-27 11:05:07 +08:00
Mathias Agopian 772136decd fix a typo/bad merge that broke setPresentationTime (#9452)
FIXES=[462533574]
2025-11-21 11:40:17 -08:00
Powei Feng f664601c51 android: add readPixels callback for ModelViewer (#9440) 2025-11-21 19:33:36 +00:00
Ben Doherty f06b27b7fb Fix getter methods (#9450) 2025-11-21 00:37:28 +00:00
Mathias Agopian ef53ce88d4 don't assume compositor and frame timing are always available (#9449)
- this is not true fro headless swapchains
- and potentially some timings might not be available on a given nativewindow

Previously the code would handle these errors gracefully, but the 
errors were still generated. Now, we query the availability and only
make the calls  if supported.

Also on EGL, we don't attempt to use private APIs -- this code path
should never be used anyways.
2025-11-20 15:32:02 -08:00
Mathias Agopian ef18030e1a frameId must be monotonic in the SwapChain (#9447)
The frameId coming from a Renderer must be monotonic when seen from
a SwapChain (Specifically a ANativeWindow on Android), if it's not
the case, we must clear that part of the history.

This can happen if a SwapChain is used with two different Renderer; at
this point that SwapChain's history is no longer connected to that
Renderer.
2025-11-20 13:23:58 -08:00
Doris Wu f07176c0a2 Try to fix test_UboBatching (#9448) 2025-11-20 15:14:19 +08:00
Doris Wu 15db141c7a Add some unit tests for UboManager (#9446) 2025-11-20 14:28:13 +08:00
Doris Wu d4bbb7c591 buffer update opt: Some optimizations (#9438) 2025-11-20 00:52:04 +00:00
Sungun Park 92e620d2ad Simplify buffer object creation (#9436)
Simplify the buffer object creation logic to streamline and help make
the future integration of asynchronous features easier.
2025-11-19 22:03:05 +00:00
Mathias Agopian 311104da97 update google benchmark library to 1.9.4 (#9441)
* benchmark: update README and add update script

* update google benchmark library to 1.9.4

* update tnt CMakeLists to match the library new version
2025-11-19 11:50:34 -08:00
Filament Bot 3127632f96 [automated] Updating /docs due to commit 59f611b
Full commit hash is 59f611bfde

DOCS_ALLOW_DIRECT_EDITS
2025-11-19 19:32:56 +00:00
Sungun Park 59f611bfde Release Filament 1.67.1 2025-11-19 11:30:16 -08:00
Sungun Park 2d556bdca2 Workaround for screen space reflections artifacts fix (#9444)
This is a workaround for Samsung Galaxy S24 device failing to link this change.
https://github.com/google/filament/commit/ea608d409edd71febe85b683a90642597149a3e9
2025-11-19 19:03:00 +00:00
Ben Doherty 65e7dba8b8 Log GPU errors through Platform debugStat API (#9434) 2025-11-19 10:42:42 -08:00
Mathias Agopian 6550a63056 Fix android helper header usage (#9442)
These headers are private but were used by public headers. 
Refactor the platform code so that these headers stay private.

AndroidNdk.h is treated differently, it is now made public because
it simplifies a lot of things to be able to have the platform concrete
implementation derive from it.
2025-11-19 10:20:37 -08:00
Doris Wu aa4e2c56b5 buffer update opt: Skip releaseFreeSlots when no frees are pending (#9437) 2025-11-15 10:23:27 +08:00
Powei Feng aa4f1910b8 android: add static annotation for Utils.init() (#9435)
This allows for static call from java, e.g.:

```
static {
    Utils.init();
}
```
2025-11-13 22:01:49 +00:00
Mathias Agopian 223a4b18a8 Fix several problems with RenderPass descriptor sets (#9431)
The root of the problem is that in the main rendering loop we need
to set the correct per-view descriptor-set based on the material and
variants.

But we have two cases, either the descriptor set is always constant, 
which is the case with ssr, structure, shadows and postfx passes, or
it need to be dynamically changed based on the material & variant.

In the 2nd case, where was a problem where the postfx descriptor set
could be used, which is wrong (e.g. while rendering shadows we need
the shadow UBO, even with a postfx material), and would corrupt the
correct descriptor set, which would never be set back.

Another issue was related to running a custom command, it could 
change the state without updating the local copy, causing corruptions
or validation errors.

In this CL we:
- use the same descriptorsetlayout for both postfx and depth
- invalidate the state after a custom command
- only switch to postfx descriptor set in dynamic mode (color pass)


This fixes setChannelDepthClearEnabled() which could cause 
validation error, corruption or crashes.

FIXES=[459567258]
2025-11-13 12:07:16 -08:00
rafadevai 57ef534acd VK: Add support for getting pipeline creation stats (#9422)
* VK: Add support for getting pipeline creation stats

When debugging is pipeline compilation related hitches
is useful to know more information about what happened
when building a new pipeline.

By levering the VK_EXT_pipeline_creation_feedback
extension, we can know if the pipeline creation was
speed up because of a cache hit, how much time it spent
creating it and also the stats related to each
pipeline stage.

This is only available when using the debug flag
FVK_DEBUG_SHADER_MODULE.

* Fix the unused build error
2025-11-13 09:01:02 -08:00
rafadevai 719914fb84 VK: Better support for renderdoc captures (#9423)
Doing a renderdoc capture when using transient memory
or importing AHardwareBuffers into Vulkan, makes it fail,
crash or not replay properly.

To improve the capture support, added a new constant to let the
backend know when renderdoc is expected to be used and configure
things appropietly.

- In the case of transient attachments that uses lazy allocated
memory, it will be disabled.
- In the case of AHardwareBuffers, a more robust memory heap
selection algorithm will be used.

Also a new VulkanPlatform function will be added, to let other
subclasses know that the transient attachments are enabled or not.
2025-11-13 07:40:51 -08:00
Powei Feng 080f958da3 android: remove checked in asset (#9426)
For sample-gltf-viewer, an asset was checked into the source tree.
But other assets are generated or copied from asset directories.

We remove the checked in gltf and copy the asset over during
build (as with other existing files).
2025-11-13 06:29:45 +00:00
Doris Wu 7547aa3807 buffer update opt: Integrate UboManager into the engine (#9397) 2025-11-13 02:19:48 +00:00
Sungun Park 67a0c6e0e1 fix a typo in VertexBuffer (#9427) 2025-11-12 15:31:55 -08:00
Ben Doherty 37cb842993 Fix possible null string crash inside TextWriter (#9428) 2025-11-12 12:07:16 -08:00
Doris Wu ad27d48fd3 buffer update opt: refactor UboManager and introduce FenceManager (#9418) 2025-11-11 16:56:06 +08:00
Mathias Agopian 3a503976c8 add View::getLastDynamicResolutionScale() (#9419)
* add View::getLastDynamicResolutionScale()

This method return the last dynamic resolution scaling factor used
by the view.

FIXES=[457753622]
2025-11-10 14:49:23 -08:00
Mathias Agopian ce6fa82026 improve camera precondition checks and default state (#9420)
* improve camera precondition checks and default state

- Camera is now initialized with a default projection matrix with
left/right, top/bottom set to (-1,1) and near/far set to (0.1,1).

- setLensProjection() and setProjection(FOV) now check the near/far
precondition (near>0 and far>near).

- sanitize user's light near/far to avoid infs and nans during
  froxelization

- finally froxelizer asserts that near>0 and far>near on debug builds.

FIXES=[458030186]
2025-11-10 14:48:48 -08:00
Mathias Agopian e8349ab5cc AndroidNativeWindow must also work on 32-bits architectures (#9424) 2025-11-10 14:44:52 -08:00
yein 4773fc4647 Move the include resolution functionality to matp (#9414)
* Move the include resolution functionality to matp

- matp::MaterialParser now has a function resolveIncludes that returns a pair of status and resolve string.
- all the include resolution classes are moved to matp private src.
- matp::resolveIncludes is renamed to matp::resolveIncludesRecursively and only used internally.
- added insertLineDirectives and insertLineDirectiveChecks in Config; add those in the CommandlineConfig.
- moved output format to public use.

Note: MaterialParser::resolveIncludes could take a includer instead of the materialFilePath, but i decided
to go with the materialFilePath because the most common use case is resolving from the file's directory.
This allows the parser to just create the default DirIncluder internally, and we don't need to expose it publicly.

* add const to the buffer param
2025-11-07 15:23:54 -08:00
Rafael Marinho de Araújoandrma6 d47a69a529 add overload of loadTexture that loads bitmap from ByteArray (#9416)
Co-authored-by: rma6 <rafaelmarinhoa@gmail.com>
2025-11-07 10:11:23 -08:00
Powei Feng d3b74e96b4 vk: fix thread-safety wrt queryFrameTimestamps() (#9415)
queryFrameTimestamps() and queryCompositorTiming() are both
synchronous APIs, but VuklanSwapchain is not a thread-safe
ref-counted handle (i.e. fvkmemory::ThreadSafeResource). We move
the Platform::SwapChain* pointers to a thread-safe map that is
mapped by the handle id.
2025-11-07 18:00:01 +00:00
Mathias Agopian 209d3f7550 Fix a race condition when tearing down FrameInfo (#9413)
* HandleAllocator::deallocate() was unsafe

It needs to know the concrete type to call the proper destructor, so
if it was given a base type handle (e.g. Handle<HwFoo>) it would not
destroy it properly.

* add AsyncJobQueue::cancelAll()

* Fix a race condition when tearing down FrameInfo

It is actually invalid to destroy a Handle<HwFence> while inside
fenceWait().

Updated the HwFence implementations so that they don't pretend they can
handle being destroyed during fenceWait(), they can't.

FrameInfo now cancels all the pending callbacks and waits for the 
currently executing one to terminate, *before* destroying the
handles.

Reenable the gpuFrameComplete metric, as it should be working now.
2025-11-06 16:50:08 -08:00
Mathias Agopian 92e65cb3fd Properly use SDK 26+ APIs (#9417)
With minSDK less than 26, it's unsafe to use APIs in nativewindow.h 
that appeared at API 26 or more, even when using the weak symbol
technique. This is because pre 26, libnativewindow.h didn't exist and
therefore the build system cannot link against it.

Currently Filament's minSdk is 21, which means we should never use
nativewindow.h API after 26.

In this PR we chose the strategy at compile time; either the more
modern weak symbol or the old way with dlsym based on __ANDROID_API__.
2025-11-06 16:49:28 -08:00
Powei Feng a4945939de vk: fix leaking swapchains again (#9410)
- Adjust order of destroy call in both headless and platform
   swapchains.  We need to be careful of the order due to
   assumptions made by the base class's destroy().
 - remove `=0` since VulkanPlatformSwapChainBase::destroy() has
   an implementation.

Fixes #9403
2025-11-05 22:10:05 -08:00
Benjamin Doherty 086760b307 Add missing mutex header 2025-11-05 15:35:29 -08:00
Powei Feng ce1b63ce38 gltf_viewer: fix accidentally removed FilamentAppVulkanPlatform 2025-11-05 15:26:47 -08:00
Mathias Agopian b7b8983653 add a feature flag for FrameInfo::gpuFrameComplete (#9409)
There are several race conditions caused by this feature. So we add
this flag and disable it by default, for now.
2025-11-05 15:22:43 -08:00
Powei Feng d52fb1f4fd vk: refactor to use polymorphism for platform specialization (#9398)
We refactor VulkanPlatform so that getSwapchainInstanceExtensions()
and createVkSurfaceKHR() are virtual functions that are implemented
by each platform. This will enable the use of polymorphism for
platform-dependent bits.
2025-11-05 19:44:39 +00:00
Powei Feng 901c87761b gltfio: add null check for when image is not provided (#9407)
Fixes #9402
2025-11-05 19:26:24 +00:00
Benjamin Doherty 081fe6a434 Bump MATERIAL_VERSION to 67 2025-11-05 10:35:14 -08:00
300 changed files with 22093 additions and 8596 deletions
+1 -1
View File
@@ -179,7 +179,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: ./.github/actions/linux-prereq - uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
run: ./build.sh -W debug test_filamat filament run: ./build.sh -W debug test_filamat filament gltf_viewer
- name: Run test - name: Run test
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl* run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
+6
View File
@@ -7,6 +7,12 @@ 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 Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.68.0
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
- Metal: report GPU errors to the platform via `debugUpdateStat` (b/431665753).
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
## v1.67.1 ## v1.67.1
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag. - Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
@@ -147,6 +147,15 @@ Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*, jcla
view->setDynamicResolutionOptions(options); view->setDynamicResolutionOptions(options);
} }
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nGetLastDynamicResolutionScale(JNIEnv *env, jclass, jlong nativeView, jfloatArray out_) {
jfloat* out = env->GetFloatArrayElements(out_, nullptr);
View *view = (View *) nativeView;
math::float2 result = view->getLastDynamicResolutionScale();
std::copy_n(result.v, 2, out);
env->ReleaseFloatArrayElements(out_, out, 0);
}
extern "C" JNIEXPORT void JNICALL extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) { Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) {
View* view = (View*) nativeView; View* view = (View*) nativeView;
@@ -99,6 +99,15 @@ final class Asserts {
} }
} }
@NonNull @Size(min = 2)
static float[] assertFloat2(@Nullable float[] out) {
if (out == null) out = new float[2];
else if (out.length < 2) {
throw new ArrayIndexOutOfBoundsException("Array length must be at least 2");
}
return out;
}
@NonNull @Size(min = 4) @NonNull @Size(min = 4)
static float[] assertFloat4(@Nullable float[] out) { static float[] assertFloat4(@Nullable float[] out) {
if (out == null) out = new float[4]; if (out == null) out = new float[4];
@@ -25,7 +25,6 @@ import java.util.EnumSet;
import static com.google.android.filament.Asserts.assertFloat3In; import static com.google.android.filament.Asserts.assertFloat3In;
import static com.google.android.filament.Asserts.assertFloat4In; import static com.google.android.filament.Asserts.assertFloat4In;
import static com.google.android.filament.Colors.LinearColor;
import com.google.android.filament.proguard.UsedByNative; import com.google.android.filament.proguard.UsedByNative;
@@ -674,6 +673,21 @@ public class View {
return mDynamicResolution; return mDynamicResolution;
} }
/**
* Returns the last dynamic resolution scale factor used by this view. This value is updated
* when Renderer::render(View*) is called
* @param out A 2-float array where the value will be stored, or null in which case the array is
* allocated.
* @return A 2-float array containing the horizontal and the vertical scale factors
* @see Renderer#render(View)
*/
@NonNull @Size(min = 2)
public float[] getLastDynamicResolutionScale(@Nullable @Size(min = 2) float[] out) {
out = Asserts.assertFloat2(out);
nGetLastDynamicResolutionScale(getNativeObject(), out);
return out;
}
/** /**
* Sets the rendering quality for this view (e.g. color precision). * Sets the rendering quality for this view (e.g. color precision).
* *
@@ -1317,6 +1331,7 @@ public class View {
private static native void nSetDithering(long nativeView, int dithering); private static native void nSetDithering(long nativeView, int dithering);
private static native int nGetDithering(long nativeView); private static native int nGetDithering(long nativeView);
private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality); private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality);
private static native void nGetLastDynamicResolutionScale(long nativeView, float[] out);
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality); private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar); private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
private static native void nSetShadowType(long nativeView, int type); private static native void nSetShadowType(long nativeView, int type);
@@ -16,6 +16,9 @@
package com.google.android.filament.utils package com.google.android.filament.utils
import android.graphics.Bitmap
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent import android.view.MotionEvent
import android.view.Surface import android.view.Surface
import android.view.SurfaceView import android.view.SurfaceView
@@ -26,6 +29,7 @@ import com.google.android.filament.android.UiHelper
import com.google.android.filament.gltfio.* import com.google.android.filament.gltfio.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.nio.Buffer import java.nio.Buffer
import java.nio.ByteBuffer
private const val kNearPlane = 0.05f // 5 cm private const val kNearPlane = 0.05f // 5 cm
private const val kFarPlane = 1000.0f // 1 km private const val kFarPlane = 1000.0f // 1 km
@@ -119,6 +123,8 @@ class ModelViewer(
private val target = DoubleArray(3) private val target = DoubleArray(3)
private val upward = DoubleArray(3) private val upward = DoubleArray(3)
private var debugFrameCallback: ((Bitmap) -> Unit)? = null
init { init {
renderer = engine.createRenderer() renderer = engine.createRenderer()
scene = engine.createScene() scene = engine.createScene()
@@ -305,10 +311,39 @@ class ModelViewer(
// Render the scene, unless the renderer wants to skip the frame. // Render the scene, unless the renderer wants to skip the frame.
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) { if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
renderer.render(view) renderer.render(view)
debugFrameCallback?.let {
val viewport = view.viewport
val bitmap = Bitmap.createBitmap(viewport.width, viewport.height,
Bitmap.Config.ARGB_8888)
val buffer = ByteBuffer.allocateDirect(viewport.width * viewport.height * 4)
val handler = Handler(Looper.getMainLooper())
val pixelBufferDescriptor = Texture.PixelBufferDescriptor(buffer,
Texture.Format.RGBA, Texture.Type.UBYTE, 1, 0, 0, 0, handler) {
buffer.rewind()
bitmap.copyPixelsFromBuffer(buffer)
it(bitmap)
}
renderer.readPixels(viewport.left, viewport.bottom, viewport.width,
viewport.height, pixelBufferDescriptor)
debugFrameCallback = null
}
renderer.endFrame() renderer.endFrame()
} }
} }
/*
* Sets a callback that will be invoked with the next rendered frame as a Bitmap. Note that this
* is a one-time callback.
*
* @param callback callback to be invoked with a rendered frame as [Bitmap]
*/
fun debugGetNextFrameCallback(callback: (Bitmap) -> Unit) {
debugFrameCallback = callback
}
private fun populateScene(asset: FilamentAsset) { private fun populateScene(asset: FilamentAsset) {
val rcm = engine.renderableManager val rcm = engine.renderableManager
var count = 0 var count = 0
@@ -40,7 +40,21 @@ fun loadTexture(engine: Engine, resources: Resources, resourceId: Int, type: Tex
options.inPremultiplied = type == TextureType.COLOR options.inPremultiplied = type == TextureType.COLOR
val bitmap = BitmapFactory.decodeResource(resources, resourceId, options) val bitmap = BitmapFactory.decodeResource(resources, resourceId, options)
return buildTexture(engine, bitmap, type)
}
fun loadTexture(engine: Engine, bytes: ByteArray, type: TextureType, offset: Int = 0, length: Int = bytes.size): Texture {
val options = BitmapFactory.Options()
// Color is the only type of texture we want to pre-multiply with the alpha channel
// Pre-multiplication is the default behavior, so we need to turn it off here
options.inPremultiplied = type == TextureType.COLOR
val bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options)
return buildTexture(engine, bitmap, type)
}
private fun buildTexture(engine: Engine, bitmap: Bitmap, type: TextureType): Texture {
val texture = Texture.Builder() val texture = Texture.Builder()
.width(bitmap.width) .width(bitmap.width)
.height(bitmap.height) .height(bitmap.height)
@@ -22,6 +22,7 @@ object Utils {
/** /**
* Initializes the utils JNI layer. Must be called before using any utils functionality. * Initializes the utils JNI layer. Must be called before using any utils functionality.
*/ */
@JvmStatic
fun init() { fun init() {
// Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv. // Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv.
Filament.init() Filament.init()
@@ -17,12 +17,20 @@ filamentTools {
} }
// don't forget to update MainACtivity.kt when/if changing this. // don't forget to update MainACtivity.kt when/if changing this.
tasks.register('copyMesh', Copy) { tasks.register('copyDamagedHelmetGltf', Copy) {
from file("../../../third_party/models/DamagedHelmet/DamagedHelmet.glb")
into file("src/main/assets/models")
rename {String fileName -> "helmet.glb"}
}
// don't forget to update MainACtivity.kt when/if changing this.
tasks.register('copyBusterGltf', Copy) {
from "../../../third_party/models/BusterDrone" from "../../../third_party/models/BusterDrone"
into "src/main/assets/models" into "src/main/assets/models"
} }
preBuild.dependsOn copyMesh preBuild.dependsOn copyDamagedHelmetGltf
preBuild.dependsOn copyBusterGltf
clean.doFirst { clean.doFirst {
delete "src/main/assets" delete "src/main/assets"
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.66.2' implementation 'com.google.android.filament:filament-android:1.67.1'
} }
</code></pre> </code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p> <p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div> </div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3> <h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p> <p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.66.2' <pre><code class="language-shell">pod 'Filament', '~&gt; 1.67.1'
</code></pre> </code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2> <h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul> <ul>
+1821 -1790
View File
File diff suppressed because it is too large Load Diff
+1201 -1174
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14 -3
View File
@@ -252,10 +252,21 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/utils/Spirv.h src/vulkan/utils/Spirv.h
src/vulkan/utils/StaticVector.h src/vulkan/utils/StaticVector.h
) )
if (LINUX OR WIN32) if (LINUX)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp) list(APPEND SRCS
src/vulkan/platform/VulkanPlatformLinux.cpp
include/backend/platforms/VulkanPlatformLinux.h
)
elseif (WIN32)
list(APPEND SRCS
include/backend/platforms/VulkanPlatformWindows.h
src/vulkan/platform/VulkanPlatformWindows.cpp
)
elseif (APPLE OR IOS) elseif (APPLE OR IOS)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformApple.mm) list(APPEND SRCS
include/backend/platforms/VulkanPlatformApple.h
src/vulkan/platform/VulkanPlatformApple.mm
)
elseif (ANDROID) elseif (ANDROID)
list(APPEND SRCS list(APPEND SRCS
include/backend/platforms/VulkanPlatformAndroid.h include/backend/platforms/VulkanPlatformAndroid.h
+37 -7
View File
@@ -19,13 +19,17 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H #ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
#define TNT_FILAMENT_BACKEND_PLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORM_H
#include <utils/CString.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/Invocable.h> #include <utils/Invocable.h>
#include <utils/Mutex.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <atomic> #include <atomic>
#include <memory>
#include <mutex>
namespace filament::backend { namespace filament::backend {
@@ -508,13 +512,22 @@ public:
// -------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------
// Debugging APIs // Debugging APIs
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>; using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key,
uint64_t intValue, utils::CString stringValue)>;
/** /**
* Sets the callback function that the backend can use to update backend-specific statistics * Sets the callback function that the backend can use to update backend-specific statistics
* to aid with debugging. This callback is guaranteed to be called on the Filament driver * to aid with debugging. This callback is guaranteed to be called on the Filament driver
* thread. * thread.
* *
* The callback signature is (key, intValue, stringValue). Note that for any given call,
* only one of the value parameters (intValue or stringValue) will be meaningful, depending on
* the specific key.
*
* IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking
* work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should
* be made, which could interfere with Filament's driver state.
*
* @param debugUpdateStat an Invocable that updates debug statistics * @param debugUpdateStat an Invocable that updates debug statistics
*/ */
void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept; void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept;
@@ -533,15 +546,32 @@ public:
* This function is guaranteed to be called only on a single thread, the Filament driver * This function is guaranteed to be called only on a single thread, the Filament driver
* thread. * thread.
* *
* @param key a null-terminated C-string with the key of the debug statistic * @param key a null-terminated C-string with the key of the debug statistic
* @param value the updated value of key * @param intValue the updated integer value of key (the string value passed to the
* callback will be empty)
*/ */
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value); void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t intValue);
/**
* To track backend-specific statistics, the backend implementation can call the
* application-provided callback function debugUpdateStatFunc to associate or update a value
* with a given key. It is possible for this function to be called multiple times with the
* same key, in which case newer values should overwrite older values.
*
* This function is guaranteed to be called only on a single thread, the Filament driver
* thread.
*
* @param key a null-terminated C-string with the key of the debug statistic
* @param stringValue the updated string value of key (the integer value passed to the
* callback will be 0)
*/
void debugUpdateStat(const char* UTILS_NONNULL key, utils::CString stringValue);
private: private:
InsertBlobFunc mInsertBlob; std::shared_ptr<InsertBlobFunc> mInsertBlob;
RetrieveBlobFunc mRetrieveBlob; std::shared_ptr<RetrieveBlobFunc> mRetrieveBlob;
DebugUpdateStatFunc mDebugUpdateStat; std::shared_ptr<DebugUpdateStatFunc> mDebugUpdateStat;
mutable utils::Mutex mMutex;
}; };
} // namespace filament } // namespace filament
@@ -17,8 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
#include "AndroidSwapChainHelper.h"
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h" #include "AndroidNdk.h"
#include <backend/AcquiredImage.h> #include <backend/AcquiredImage.h>
@@ -171,19 +169,10 @@ protected:
SwapChain* drawSwapChain, SwapChain* drawSwapChain,
SwapChain* readSwapChain) override; SwapChain* readSwapChain) override;
struct SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
private:
AndroidSwapChainHelper mImpl{};
};
private: private:
struct SwapChainEGLAndroid;
struct AndroidDetails;
// prevent derived classes' implementations to call through // prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override; [[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override; [[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
@@ -203,17 +192,14 @@ private:
int mOSVersion; int mOSVersion;
ExternalStreamManagerAndroid& mExternalStreamManager; ExternalStreamManagerAndroid& mExternalStreamManager;
AndroidDetails& mAndroidDetails;
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded; InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager; utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession; utils::PerformanceHintManager::Session mPerformanceHintSession;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
using clock = std::chrono::high_resolution_clock; using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork; clock::time_point mStartTimeOfActualWork;
AndroidProducerThrottling mProducerThrottling; SwapChainEGLAndroid* mCurrentDrawSwapChain{};
bool mAssertNativeWindowIsValid = false; bool mAssertNativeWindowIsValid = false;
AndroidFrameCallback mAndroidFrameCallback;
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -437,22 +437,19 @@ protected:
std::shared_ptr<VulkanCmdFence> fenceStatus; std::shared_ptr<VulkanCmdFence> fenceStatus;
}; };
virtual ExtensionSet getSwapchainInstanceExtensions() const;
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>; using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
virtual ExtensionSet getSwapchainInstanceExtensions() const = 0;
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept; uint64_t flags) const noexcept = 0;
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept; virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
/**
* Query if transient attachments are supported by the backend.
*/
bool isTransientAttachmentSupported() const noexcept;
private: private:
// Platform dependent helper methods
static ExtensionSet getSwapchainInstanceExtensionsImpl();
// Platform dependent helper methods
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
uint64_t flags) noexcept;
friend struct VulkanPlatformPrivate; friend struct VulkanPlatformPrivate;
}; };
@@ -17,12 +17,13 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H #ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H #define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h" #include "AndroidNdk.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatform.h>
#include <utils/compiler.h>
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
namespace filament::backend { namespace filament::backend {
@@ -91,6 +92,8 @@ protected:
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override; VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
private: private:
struct AndroidDetails;
struct ExternalImageVulkanAndroid : public ExternalImage { struct ExternalImageVulkanAndroid : public ExternalImage {
AHardwareBuffer* aHardwareBuffer = nullptr; AHardwareBuffer* aHardwareBuffer = nullptr;
bool sRGB = false; bool sRGB = false;
@@ -99,7 +102,7 @@ private:
~ExternalImageVulkanAndroid() override; ~ExternalImageVulkanAndroid() override;
}; };
AndroidFrameCallback mAndroidFrameCallback; AndroidDetails& mAndroidDetails;
int mOSVersion{}; int mOSVersion{};
}; };
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
#include <backend/platforms/VulkanPlatform.h>
namespace filament::backend {
class VulkanPlatformApple : public VulkanPlatform {
protected:
ExtensionSet getSwapchainInstanceExtensions() const override;
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept override;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
#include <backend/platforms/VulkanPlatform.h>
namespace filament::backend {
class VulkanPlatformLinux : public VulkanPlatform {
protected:
ExtensionSet getSwapchainInstanceExtensions() const override;
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept override;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
#include <backend/platforms/VulkanPlatform.h>
namespace filament::backend {
class VulkanPlatformWindows : public VulkanPlatform {
protected:
ExtensionSet getSwapchainInstanceExtensions() const override;
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept override;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
@@ -61,6 +61,16 @@ int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable); return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable);
} }
int NativeWindow::frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent) {
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
int value = 0;
bool const success = pWindow->perform(anw, FRAME_TIMESTAMPS_SUPPORTS_PRESENT, &value);
if (success) {
*outSupportsPresent = bool(value);
}
return success;
}
int NativeWindow::getCompositorTiming(ANativeWindow* anw, int NativeWindow::getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval, int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency) { int64_t* compositeToPresentLatency) {
@@ -32,6 +32,7 @@ struct NativeWindow {
// is valid query enum value // is valid query enum value
enum { enum {
IS_VALID = 17, IS_VALID = 17,
FRAME_TIMESTAMPS_SUPPORTS_PRESENT = 18,
GET_NEXT_FRAME_ID = 24, GET_NEXT_FRAME_ID = 24,
ENABLE_FRAME_TIMESTAMPS = 25, ENABLE_FRAME_TIMESTAMPS = 25,
GET_COMPOSITOR_TIMING = 26, GET_COMPOSITOR_TIMING = 26,
@@ -51,6 +52,7 @@ struct NativeWindow {
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId); static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
static int enableFrameTimestamps(ANativeWindow* anw, bool enable); static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
static int frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent);
static int getCompositorTiming(ANativeWindow* anw, static int getCompositorTiming(ANativeWindow* anw,
int64_t* compositeDeadline, int64_t* compositeInterval, int64_t* compositeDeadline, int64_t* compositeInterval,
int64_t* compositeToPresentLatency); int64_t* compositeToPresentLatency);
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include "AndroidNdk.h" #include <backend/platforms/AndroidNdk.h>
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
@@ -19,6 +19,9 @@
#include <android/native_window.h> #include <android/native_window.h>
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <limits> #include <limits>
@@ -36,11 +39,23 @@ bool AndroidSwapChainHelper::setPresentFrameId(
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId); int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
if (status == 0) { if (status == 0) {
std::lock_guard const lock(mLock); std::lock_guard const lock(mLock);
auto const pos = mFrameIdToSystemFrameId.find(frameId); // frameIds must be strictly monotonic, if that's not the case (i.e. the new frameId is
if (pos && *pos != sysFrameId) { // less or equal to the last one in the map), we have to clear the map, because the
// we're trying to associate the same frame id to a different frame! // map's find() assume sorted keys.
return false; // This case can happen if two different filament::Renderer are used with the same
// ANativeWindow (the Renderer would have different frameIds). This is expected to
// be a rare case.
if (UTILS_UNLIKELY(!mFrameIdToSystemFrameId.empty() &&
frameId <= mFrameIdToSystemFrameId.back().first)) {
// this log is expected to happen very rarely
DLOG(INFO) << "clearing frame history anw=" << anw
<< ", frameId=" << frameId
<< ", previous=" << mFrameIdToSystemFrameId.back().first
<< ", sysFrameId=" << sysFrameId;
// clear the frame history
mFrameIdToSystemFrameId.clear();
} }
// oldest entry is removed // oldest entry is removed
mFrameIdToSystemFrameId.insert(frameId, sysFrameId); mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
return true; return true;
+44 -13
View File
@@ -139,42 +139,73 @@ bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*
} }
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept { void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
mInsertBlob = std::move(insertBlob); std::lock_guard<decltype(mMutex)> lock(mMutex);
mRetrieveBlob = std::move(retrieveBlob); mInsertBlob = std::make_shared<InsertBlobFunc>(std::move(insertBlob));
mRetrieveBlob = std::make_shared<RetrieveBlobFunc>(std::move(retrieveBlob));
} }
bool Platform::hasInsertBlobFunc() const noexcept { bool Platform::hasInsertBlobFunc() const noexcept {
return bool(mInsertBlob); std::lock_guard<decltype(mMutex)> lock(mMutex);
return mInsertBlob && bool(*mInsertBlob);
} }
bool Platform::hasRetrieveBlobFunc() const noexcept { bool Platform::hasRetrieveBlobFunc() const noexcept {
return bool(mRetrieveBlob); std::lock_guard<decltype(mMutex)> lock(mMutex);
return mRetrieveBlob && bool(*mRetrieveBlob);
} }
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) { void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
if (mInsertBlob) { std::shared_ptr<InsertBlobFunc> callback;
mInsertBlob(key, keySize, value, valueSize); {
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mInsertBlob;
}
if (callback) {
(*callback)(key, keySize, value, valueSize);
} }
} }
size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) { size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) {
if (mRetrieveBlob) { std::shared_ptr<RetrieveBlobFunc> callback;
return mRetrieveBlob(key, keySize, value, valueSize); {
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mRetrieveBlob;
}
if (callback) {
return (*callback)(key, keySize, value, valueSize);
} }
return 0; return 0;
} }
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept { void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
mDebugUpdateStat = std::move(debugUpdateStat); std::lock_guard<decltype(mMutex)> lock(mMutex);
mDebugUpdateStat = std::make_shared<DebugUpdateStatFunc>(std::move(debugUpdateStat));
} }
bool Platform::hasDebugUpdateStatFunc() const noexcept { bool Platform::hasDebugUpdateStatFunc() const noexcept {
return bool(mDebugUpdateStat); std::lock_guard<decltype(mMutex)> lock(mMutex);
return mDebugUpdateStat && bool(*mDebugUpdateStat);
} }
void Platform::debugUpdateStat(const char* key, uint64_t value) { void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
if (mDebugUpdateStat) { std::shared_ptr<DebugUpdateStatFunc> callback;
mDebugUpdateStat(key, value); {
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mDebugUpdateStat;
}
if (callback) {
(*callback)(key, intValue, "");
}
}
void Platform::debugUpdateStat(const char* key, utils::CString stringValue) {
std::shared_ptr<DebugUpdateStatFunc> callback;
{
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mDebugUpdateStat;
}
if (callback) {
(*callback)(key, 0, stringValue);
} }
} }
+13 -3
View File
@@ -67,8 +67,12 @@
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include "backend/platforms/VulkanPlatformAndroid.h" #include "backend/platforms/VulkanPlatformAndroid.h"
#else #elif defined(__APPLE__)
#include "backend/platforms/VulkanPlatform.h" #include "backend/platforms/VulkanPlatformApple.h"
#elif defined(__linux__)
#include "backend/platforms/VulkanPlatformLinux.h"
#elif defined(WIN32)
#include "backend/platforms/VulkanPlatformWindows.h"
#endif #endif
#endif #endif
@@ -117,8 +121,14 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#if defined(__ANDROID__) #if defined(__ANDROID__)
return new VulkanPlatformAndroid(); return new VulkanPlatformAndroid();
#elif defined(__APPLE__)
return new VulkanPlatformApple();
#elif defined(__linux__)
return new VulkanPlatformLinux();
#elif defined(WIN32)
return new VulkanPlatformWindows();
#else #else
return new VulkanPlatform(); return nullptr;
#endif #endif
#else #else
return nullptr; return nullptr;
+3 -6
View File
@@ -165,7 +165,7 @@ public:
size_t size, bool forceGpuBuffer = false); size_t size, bool forceGpuBuffer = false);
~MetalBuffer(); ~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; } [[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete; MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete; MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
@@ -185,14 +185,12 @@ public:
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid * Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
* until the end of the current frame. * until the end of the current frame.
* *
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there * @return The MTLBuffer representing the current state of the buffer to bind, it never returns
* is no device allocation. * nil.
* *
*/ */
id<MTLBuffer> getGpuBufferForDraw() noexcept; id<MTLBuffer> getGpuBufferForDraw() noexcept;
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
void setLabel(const utils::ImmutableCString& label) { void setLabel(const utils::ImmutableCString& label) {
#if FILAMENT_METAL_DEBUG_LABELS #if FILAMENT_METAL_DEBUG_LABELS
if (label.empty()) { if (label.empty()) {
@@ -235,7 +233,6 @@ private:
UploadStrategy mUploadStrategy; UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer; TrackedMetalBuffer mBuffer;
size_t mBufferSize = 0; size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
MetalContext& mContext; MetalContext& mContext;
}; };
+13 -63
View File
@@ -39,34 +39,30 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
mUploadStrategy = UploadStrategy::POOL; mUploadStrategy = UploadStrategy::POOL;
} }
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit MTLResourceOptions options = MTLResourceStorageModePrivate;
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
// This won't work for SSBOs, since they are read/write.
/* // The buffer will be memory mapped for write operations.
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE && if (any(usage & BufferUsage::SHARED_WRITE_BIT)) {
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) { #if defined(FILAMENT_IOS) || defined(__arm64__) || defined(__aarch64__)
mBuffer = nil; // iOS and Apple Silicon devices use UMA (Unified Memory Architecture), so we use Shared memory.
mCpuBuffer = malloc(size); options = MTLResourceStorageModeShared;
return; #else
// Intel Macs require Managed memory for CPU/GPU synchronization.
options = MTLResourceStorageModeManaged;
#endif
} }
*/
// Otherwise, we allocate a private GPU buffer.
{ {
ScopedAllocationTimer timer("generic"); ScopedAllocationTimer timer("generic");
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate], mBuffer = { [context.device newBufferWithLength:size options:options],
TrackedMetalBuffer::Type::GENERIC }; TrackedMetalBuffer::Type::GENERIC };
} }
// mBuffer might fail to be allocated. Clients can check for this by calling // mBuffer might fail to be allocated. Clients can check for this by calling
// wasAllocationSuccessful(). // wasAllocationSuccessful().
} }
MetalBuffer::~MetalBuffer() { MetalBuffer::~MetalBuffer() = default;
if (mCpuBuffer) {
free(mCpuBuffer);
}
}
void MetalBuffer::copyIntoBuffer( void MetalBuffer::copyIntoBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) { void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
@@ -83,12 +79,6 @@ void MetalBuffer::copyIntoBuffer(
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag(); << "byteOffset must be a multiple of 4, tag=" << getHandleTag();
// If we have a cpu buffer, we can directly copy into it.
if (mCpuBuffer) {
memcpy(static_cast<uint8_t*>(mCpuBuffer) + byteOffset, src, size);
return;
}
switch (mUploadStrategy) { switch (mUploadStrategy) {
case UploadStrategy::BUMP_ALLOCATOR: case UploadStrategy::BUMP_ALLOCATOR:
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag)); uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
@@ -106,11 +96,6 @@ void MetalBuffer::copyIntoBufferUnsynchronized(
} }
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept { id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
// If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound
// separately.
if (mCpuBuffer) {
return nil;
}
assert_invariant(mBuffer); assert_invariant(mBuffer);
return mBuffer.get(); return mBuffer.get();
} }
@@ -171,41 +156,6 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
offsets:metalOffsets.data() offsets:metalOffsets.data()
withRange:bufferRange]; withRange:bufferRange];
} }
for (size_t b = 0; b < count; b++) {
MetalBuffer* const buffer = buffers[b];
if (!buffer) {
continue;
}
const void* cpuBuffer = buffer->getCpuBuffer();
if (!cpuBuffer) {
continue;
}
const size_t bufferIndex = bufferStart + b;
const size_t offset = offsets[b];
auto* bytes = static_cast<const uint8_t*>(cpuBuffer);
if (stages & Stage::VERTEX) {
[(id<MTLRenderCommandEncoder>) encoder setVertexBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::FRAGMENT) {
[(id<MTLRenderCommandEncoder>) encoder setFragmentBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
if (stages & Stage::COMPUTE) {
// TODO: using setBytes means the data is read-only, which currently isn't enforced.
// In practice this won't be an issue since MetalBuffer ensures all SSBOs are realized
// through actual id<MTLBuffer> allocations.
[(id<MTLComputeCommandEncoder>) encoder setBytes:(bytes + offset)
length:(buffer->getSize() - offset)
atIndex:bufferIndex];
}
}
} }
void MetalBuffer::uploadWithPoolBuffer( void MetalBuffer::uploadWithPoolBuffer(
@@ -17,6 +17,7 @@
#ifndef TNT_METALCONTEXT_H #ifndef TNT_METALCONTEXT_H
#define TNT_METALCONTEXT_H #define TNT_METALCONTEXT_H
#include "MetalErrorQueue.h"
#include "MetalResourceTracker.h" #include "MetalResourceTracker.h"
#include "MetalShaderCompiler.h" #include "MetalShaderCompiler.h"
#include "MetalState.h" #include "MetalState.h"
@@ -129,6 +130,7 @@ struct MetalContext {
id<MTLCommandBuffer> pendingCommandBuffer = nil; id<MTLCommandBuffer> pendingCommandBuffer = nil;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil; id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
uint32_t currentFrame = 0; uint32_t currentFrame = 0;
MetalErrorQueue commandBufferErrors;
std::atomic<bool> memorylessLimitsReached = false; std::atomic<bool> memorylessLimitsReached = false;
@@ -153,6 +153,7 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) { if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
logMTLCommandBufferError(errorCode); logMTLCommandBufferError(errorCode);
context->commandBufferErrors.push(buffer.error);
} }
}]; }];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer) FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
+2
View File
@@ -61,6 +61,8 @@ public:
MetalContext* getContext() { return mContext; } MetalContext* getContext() { return mContext; }
using DriverBase::scheduleDestroy;
private: private:
friend class MetalSwapChain; friend class MetalSwapChain;
+21 -11
View File
@@ -254,6 +254,18 @@ MetalDriver::~MetalDriver() noexcept {
void MetalDriver::tick(int) { void MetalDriver::tick(int) {
executeTickOps(); executeTickOps();
executeDeferredOps(); executeDeferredOps();
// Notify platform of GPU errors.
auto& platform = mPlatform;
if (UTILS_UNLIKELY(!mContext->commandBufferErrors.isEmpty())) {
mContext->commandBufferErrors.flush([&platform](NSError* error) {
if (UTILS_VERY_UNLIKELY(!error)) {
return;
}
const utils::CString errorString(error.localizedDescription.UTF8String);
platform.debugUpdateStat("filament.metal.command_buffer_error", errorString);
});
}
} }
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -2263,7 +2275,10 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh, void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
BufferObjectHandle boh, size_t offset, BufferObjectHandle boh, size_t offset,
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) { size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access); assert_invariant(boh);
MetalBufferObject* bo = mHandleAllocator.handle_cast<MetalBufferObject*>(boh);
assert_invariant(bo);
construct_handle<MetalMemoryMappedBuffer>(mmbh, bo, offset, size, access);
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag)); mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
} }
@@ -2271,21 +2286,16 @@ void MetalDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
if (UTILS_UNLIKELY(!mmbh)) { if (UTILS_UNLIKELY(!mmbh)) {
return; return;
} }
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
mmb->unmap();
destruct_handle<MetalMemoryMappedBuffer>(mmbh); destruct_handle<MetalMemoryMappedBuffer>(mmbh);
} }
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset, void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
BufferDescriptor&& data) { BufferDescriptor&& data) {
auto mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh); auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
mmb->copy(*this, offset, std::move(data));
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mmb->size);
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
// call updateBufferObject(). This could be a fallback implementation for when
// shared memory is not available.
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
} }
// explicit instantiation of the Dispatcher // explicit instantiation of the Dispatcher
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
#define TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
#import <Foundation/Foundation.h>
#include <utils/compiler.h>
#include <atomic>
#include <functional>
#include <mutex>
#include <vector>
class MetalErrorQueue {
public:
bool isEmpty() const {
return !mHasErrors.load(std::memory_order_relaxed);
}
void push(NSError* error) {
std::lock_guard<std::mutex> lock(mMutex);
mErrors.push_back(error);
mHasErrors.store(true, std::memory_order_relaxed);
}
void flush(const std::function<void(NSError*)>& callback) {
if (UTILS_LIKELY(isEmpty())) {
return;
}
std::vector<NSError*> errors;
{
std::lock_guard<std::mutex> lock(mMutex);
std::swap(mErrors, errors);
mHasErrors.store(false, std::memory_order_relaxed);
}
for (const auto& error: errors) {
callback(error);
}
}
private:
std::vector<NSError*> mErrors;
std::mutex mMutex;
// Optimization to avoid locking the mutex at each call to flush.
std::atomic<bool> mHasErrors;
};
#endif // TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
+15 -7
View File
@@ -569,14 +569,22 @@ struct MetalDescriptorSet : public HwDescriptorSet {
struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer { struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer {
MetalMemoryMappedBuffer(BufferObjectHandle boh, size_t const offset,
size_t const size, MapBufferAccessFlags const access)
: boh(boh), access(access), size(size), offset(offset) {
}
BufferObjectHandle boh{};
MapBufferAccessFlags access{}; MapBufferAccessFlags access{};
uint32_t size = 0; struct {
uint32_t offset = 0; MetalBufferObject* bo;
void* vaddr = nullptr;
uint32_t size = 0;
uint32_t offset = 0;
} mtl;
MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept;
~MetalMemoryMappedBuffer();
void unmap();
void copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const;
}; };
} // namespace backend } // namespace backend
@@ -1664,5 +1664,44 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
return buffer.get(); return buffer.get();
} }
MetalMemoryMappedBuffer::MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
MapBufferAccessFlags access) noexcept : access(access) {
MetalBuffer* buffer = bo->getBuffer();
assert_invariant(buffer);
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
assert_invariant(offset + size <= bo->byteCount);
assert_invariant(mtlBuffer.storageMode != MTLStorageModePrivate);
mtl.bo = bo;
mtl.vaddr = static_cast<char*>(mtlBuffer.contents) + offset;
mtl.size = size;
mtl.offset = offset;
}
MetalMemoryMappedBuffer::~MetalMemoryMappedBuffer() = default;
void MetalMemoryMappedBuffer::unmap() {
#if !defined(FILAMENT_IOS) && defined(__x86_64__)
// Managed memory requires didModifyRange to synchronize changes to the GPU. This is specific to Intel Macs.
MetalBuffer* buffer = bo->getBuffer();
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
if (mtlBuffer && mtlBuffer.storageMode == MTLStorageModeManaged) {
[mtlBuffer didModifyRange:NSMakeRange(mtl.offset, mtl.size)];
}
#endif
// Shared memory on UMA systems is coherent; no explicit synchronization is required.
}
void MetalMemoryMappedBuffer::copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const {
assert_invariant(any(access & MapBufferAccessFlags::WRITE_BIT));
assert_invariant(offset + data.size <= mtl.size);
assert_invariant(mtl.vaddr);
memcpy(static_cast<char*>(mtl.vaddr) + offset, data.buffer, data.size);
mtld.scheduleDestroy(std::move(data));
}
} // namespace backend } // namespace backend
} // namespace filament } // namespace filament
@@ -28,14 +28,13 @@
#include <private/backend/VirtualMachineEnv.h> #include <private/backend/VirtualMachineEnv.h>
#include "AndroidNativeWindow.h" #include "AndroidNativeWindow.h"
#include "AndroidFrameCallback.h"
#include "AndroidSwapChainHelper.h" #include "AndroidSwapChainHelper.h"
#include "ExternalStreamManagerAndroid.h" #include "ExternalStreamManagerAndroid.h"
#include <android/api-level.h> #include <android/api-level.h>
#include <android/native_window.h> #include <android/native_window.h>
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
#include <android/choreographer.h>
#include <android/looper.h>
#include <utils/android/PerformanceHintManager.h> #include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h> #include <utils/compiler.h>
@@ -99,6 +98,27 @@ using namespace glext;
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
struct PlatformEGLAndroid::SwapChainEGLAndroid : public SwapChainEGL {
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
void* nativeWindow, uint64_t flags);
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
uint32_t width, uint32_t height, uint64_t flags);
void terminate(PlatformEGLAndroid& platform);
bool setPresentFrameId(uint64_t frameId) const noexcept;
uint64_t getFrameId(uint64_t frameId) const noexcept;
bool compositorTimingSupported = false;
bool frameTimestampsSupported = false;
private:
AndroidSwapChainHelper mImpl{};
};
struct PlatformEGLAndroid::AndroidDetails {
AndroidProducerThrottling producerThrottling;
AndroidFrameCallback androidFrameCallback;
};
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() { PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need // PerformanceHintManager() needs the calling thread to be a Java thread; so we need
// to attach this thread to the JVM before we initialize PerformanceHintManager. // to attach this thread to the JVM before we initialize PerformanceHintManager.
@@ -112,17 +132,20 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) { : mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
mOSVersion = android_get_device_api_level(); mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) { if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__; mOSVersion = __ANDROID_API_FUTURE__;
} }
} }
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default; PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
delete &mAndroidDetails;
}
void PlatformEGLAndroid::terminate() noexcept { void PlatformEGLAndroid::terminate() noexcept {
mAndroidFrameCallback.terminate(); mAndroidDetails.androidFrameCallback.terminate();
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager); ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
PlatformEGL::terminate(); PlatformEGL::terminate();
} }
@@ -207,9 +230,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
"eglGetNativeClientBufferANDROID")); "eglGetNativeClientBufferANDROID"));
if (ext.egl.ANDROID_presentation_time) { if (ext.egl.ANDROID_presentation_time) {
eglGetNativeClientBufferANDROID = eglPresentationTimeANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress( PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID")); "eglPresentationTimeANDROID"));
} }
if (ext.egl.ANDROID_get_frame_timestamps) { if (ext.egl.ANDROID_get_frame_timestamps) {
@@ -240,7 +263,7 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid; mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
mAndroidFrameCallback.init(); mAndroidDetails.androidFrameCallback.init();
return driver; return driver;
} }
@@ -264,15 +287,25 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
} }
AndroidFrameCallback::Timeline const preferredTimeline{ AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidFrameCallback.getPreferredTimeline() }; mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime; outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime; outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline; outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
// From this point on, we always return "success" because some timings were returned.
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->compositorTimingSupported) {
// if this surface doesn't support it, don't attempt to query the values.
return true;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) { if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur; EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
if (sur == EGL_NO_SURFACE) { if (sur == EGL_NO_SURFACE) {
return false; return true;
} }
std::array<EGLnsecsANDROID, 3> values; std::array<EGLnsecsANDROID, 3> values;
@@ -283,26 +316,16 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
}; };
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur, EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
names.size(), names.data(), values.data()); names.size(), names.data(), values.data());
if (!success) { if (UTILS_UNLIKELY(!success)) {
return false; // reset current error to EGL_SUCCESS
eglGetError();
} else {
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
} }
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
return true;
} }
return true;
// fallback to private APIs
auto const anw = static_cast<SwapChainEGL const *>(swapchain)->nativeWindow;
int const status = NativeWindow::getCompositorTiming(anw,
&outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeInterval,
&outCompositorTiming->compositeToPresentLatency);
if (status == 0) {
return true;
}
return PlatformEGL::queryCompositorTiming(swapchain, outCompositorTiming);
} }
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain, bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
@@ -327,6 +350,10 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
return false; return false;
} }
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->frameTimestampsSupported) {
return false;
}
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) { if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLSurface const sur = sc->sur; EGLSurface const sur = sc->sur;
if (sur == EGL_NO_SURFACE) { if (sur == EGL_NO_SURFACE) {
@@ -347,7 +374,9 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
}; };
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId, EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
names.size(), names.data(), values.data()); names.size(), names.data(), values.data());
if (!success) { if (UTILS_UNLIKELY(!success)) {
// reset current error to EGL_SUCCESS
eglGetError();
return false; return false;
} }
outFrameTimestamps->requestedPresentTime = values[0]; outFrameTimestamps->requestedPresentTime = values[0];
@@ -361,28 +390,44 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
outFrameTimestamps->releaseTime = values[8]; outFrameTimestamps->releaseTime = values[8];
return true; return true;
} }
// fallback to private APIs
auto const anw = sc->nativeWindow;
int const status = NativeWindow::getFrameTimestamps(anw, hwFrameId,
&outFrameTimestamps->requestedPresentTime,
&outFrameTimestamps->acquireTime,
&outFrameTimestamps->latchTime,
&outFrameTimestamps->firstCompositionStartTime,
&outFrameTimestamps->lastCompositionStartTime,
&outFrameTimestamps->gpuCompositionDoneTime,
&outFrameTimestamps->displayPresentTime,
&outFrameTimestamps->dequeueReadyTime,
&outFrameTimestamps->releaseTime);
if (status == 0) {
return true;
}
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps); return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
} }
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) { Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags); auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags);
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
EGLDisplay const dpy = getEglDisplay();
sc->compositorTimingSupported =
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_DEADLINE_ANDROID) &&
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_INTERVAL_ANDROID) &&
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID);
sc->frameTimestampsSupported =
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_REQUESTED_PRESENT_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_RENDERING_COMPLETE_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_COMPOSITION_LATCH_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_FIRST_COMPOSITION_START_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_LAST_COMPOSITION_START_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_DISPLAY_PRESENT_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_DEQUEUE_READY_TIME_ANDROID) &&
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
EGL_READS_DONE_TIME_ANDROID);
}
// This is expected to be a low frequency log, only turned on in debug builds
DLOG(INFO) << "anw: " << nativeWindow
<< ", compositorTimingSupported=" << sc->compositorTimingSupported
<< ", frameTimestampsSupported=" << sc->frameTimestampsSupported;
return sc; return sc;
} }
@@ -675,12 +720,12 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const { bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
return mProducerThrottling.isSupported(); return mAndroidDetails.producerThrottling.isSupported();
} }
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled( int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
EGLNativeWindowType const nativeWindow, bool const enabled) const { EGLNativeWindowType const nativeWindow, bool const enabled) const {
return mProducerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled); return mAndroidDetails.producerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
} }
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
@@ -704,8 +749,6 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
// we ignore the result, it doesn't matter much if it fails // we ignore the result, it doesn't matter much if it fails
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE); eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
} }
} else {
NativeWindow::enableFrameTimestamps(EGLNativeWindowType(nativeWindow), true);
} }
} }
@@ -214,4 +214,10 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
// destroying any unused pipeline object. // destroying any unused pipeline object.
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS); static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
// Indicates if the backend must be setup to allow doing a RenderDoc capture.
//
// If this is true, the features not supported by RenderDoc must be disabled, otherwise
// when using RenderDoc the application will crash or will fail to do a capture.
constexpr static const int FVK_RENDERDOC_CAPTURE_MODE = false;
#endif #endif
@@ -154,6 +154,10 @@ public:
return mStagingBufferBypassEnabled; return mStagingBufferBypassEnabled;
} }
inline bool pipelineCreationFeedbackSupported() const noexcept {
return mPipelineCreationFeedbackSupported;
}
private: private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {}; VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = { VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -181,6 +185,7 @@ private:
bool mProtectedMemorySupported = false; bool mProtectedMemorySupported = false;
bool mIsUnifiedMemoryArchitecture = false; bool mIsUnifiedMemoryArchitecture = false;
bool mStagingBufferBypassEnabled = false; bool mStagingBufferBypassEnabled = false;
bool mPipelineCreationFeedbackSupported = false;
fvkutils::VkFormatList mDepthStencilFormats; fvkutils::VkFormatList mDepthStencilFormats;
fvkutils::VkFormatList mBlittableDepthStencilFormats; fvkutils::VkFormatList mBlittableDepthStencilFormats;
+1 -9
View File
@@ -231,7 +231,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager), mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
mPipelineLayoutCache(mPlatform->getDevice()), mPipelineLayoutCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice()), mPipelineCache(mPlatform->getDevice(), mContext),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()), mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(mContext, mResourceManager, mAllocator), mBufferCache(mContext, mResourceManager, mAllocator),
mFramebufferCache(mPlatform->getDevice()), mFramebufferCache(mPlatform->getDevice()),
@@ -921,14 +921,6 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
} }
} }
#if defined(__ANDROID__)
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(nativeWindow), false);
}
#endif
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform, auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags); mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
swapChain.inc(); swapChain.inc();
@@ -48,10 +48,6 @@
#include <utils/Allocator.h> #include <utils/Allocator.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#if defined(__ANDROID__)
#include "AndroidNativeWindow.h"
#endif
namespace filament::backend { namespace filament::backend {
class VulkanPlatform; class VulkanPlatform;
@@ -206,9 +202,6 @@ private:
bool const mIsSRGBSwapChainSupported; bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported; bool const mIsMSAASwapChainSupported;
backend::StereoscopicType const mStereoscopicType; backend::StereoscopicType const mStereoscopicType;
#if defined(__ANDROID__)
AndroidProducerThrottling mProducerThrottling;
#endif
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -34,8 +34,41 @@ using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPipelineCache::VulkanPipelineCache(VkDevice device) namespace {
: mDevice(device) {
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
void printPipelineFeedbackInfo(VkPipelineCreationFeedbackCreateInfo const& feedbackInfo) {
VkPipelineCreationFeedback const& pipelineInfo = *feedbackInfo.pPipelineCreationFeedback;
if (!(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
return;
}
bool const isCacheHit =
(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
FVK_LOGD << "Pipeline build stats - Cache hit: " << isCacheHit
<< ", Time: " << pipelineInfo.duration / 1000000.0 << "ms";
for (uint32_t i = 0; i < feedbackInfo.pipelineStageCreationFeedbackCount; ++i) {
VkPipelineCreationFeedback const& stageInfo = feedbackInfo.pPipelineStageCreationFeedbacks[i];
if (!(stageInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
continue;
}
bool const isVertexShader = (i == 0);
bool const isCacheHit = (stageInfo.flags &
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
FVK_LOGD << (isVertexShader ? "Vertex" : "Fragment")
<< " shader build stats - Cache hit: " << isCacheHit
<< ", Time: " << stageInfo.duration / 1000000.0 << "ms";
}
}
#endif
} // namespace
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VulkanContext const& context)
: mDevice(device),
mContext(context) {
VkPipelineCacheCreateInfo createInfo = { VkPipelineCacheCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
}; };
@@ -216,15 +249,40 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
} }
} }
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) #if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[0].module << ", " << shaderStages[1].module << ")"; << shaderStages[1].module << ")";
#endif
VkPipelineCreationFeedback stageFeedbacks[SHADER_MODULE_COUNT] = {};
VkPipelineCreationFeedback pipelineFeedback = {};
VkPipelineCreationFeedbackCreateInfo feedbackInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT,
.pNext = nullptr,
.pPipelineCreationFeedback = &pipelineFeedback,
.pipelineStageCreationFeedbackCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1,
.pPipelineStageCreationFeedbacks = stageFeedbacks,
};
if (mContext.pipelineCreationFeedbackSupported()) {
feedbackInfo.pNext = pipelineCreateInfo.pNext;
pipelineCreateInfo.pNext = &feedbackInfo;
}
#endif
PipelineCacheEntry cacheEntry = { PipelineCacheEntry cacheEntry = {
.lastUsed = mCurrentTime, .lastUsed = mCurrentTime,
}; };
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo, VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
VKALLOC, &cacheEntry.handle); VKALLOC, &cacheEntry.handle);
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[1].module << ")";
if (mContext.pipelineCreationFeedbackSupported()) {
printPipelineFeedbackInfo(feedbackInfo);
}
#endif
assert_invariant(error == VK_SUCCESS); assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) { if (error != VK_SUCCESS) {
FVK_LOGE << "vkCreateGraphicsPipelines error " << error; FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
@@ -86,7 +86,7 @@ public:
static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding."); static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding.");
VulkanPipelineCache(VkDevice device); VulkanPipelineCache(VkDevice device, VulkanContext const& context);
void bindLayout(VkPipelineLayout layout) noexcept; void bindLayout(VkPipelineLayout layout) noexcept;
@@ -210,6 +210,8 @@ private:
// Current bindings for the pipeline and descriptor sets. // Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {}; PipelineKey mBoundPipeline = {};
[[maybe_unused]] VulkanContext const& mContext;
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -228,6 +228,10 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
#endif #endif
VK_KHR_MULTIVIEW_EXTENSION_NAME, VK_KHR_MULTIVIEW_EXTENSION_NAME,
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME,
#endif
}; };
ExtensionSet exts; ExtensionSet exts;
// Identify supported physical device extensions // Identify supported physical device extensions
@@ -865,6 +869,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
if (!mImpl->mSharedContext) { if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME); context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mPipelineCreationFeedbackSupported =
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
} else { } else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext; VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported; context.mDebugUtilsSupported = scontext->debugUtilsSupported;
@@ -873,13 +879,16 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
// Check the availability of lazily allocated memory // Check the availability of lazily allocated memory
context.mLazilyAllocatedMemorySupported = false; context.mLazilyAllocatedMemorySupported = false;
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount; // RenderDoc doesn't support lazy allocated memory
++i) { if constexpr (!FVK_RENDERDOC_CAPTURE_MODE) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i]; for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) { ++i) {
context.mLazilyAllocatedMemorySupported = true; VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
break; context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
} }
} }
@@ -1022,13 +1031,8 @@ VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const no
return static_cast<VkExternalFenceHandleTypeFlagBits>(0); return static_cast<VkExternalFenceHandleTypeFlagBits>(0);
} }
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const { bool VulkanPlatform::isTransientAttachmentSupported() const noexcept {
return getSwapchainInstanceExtensionsImpl(); return mImpl->mContext.isLazilyAllocatedMemorySupported();
} }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow, } // namespace filament::backend
VkInstance instance, uint64_t flags) const noexcept {
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
}
}// namespace filament::backend
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <backend/platforms/VulkanPlatformAndroid.h> #include <backend/platforms/VulkanPlatformAndroid.h>
#include "vulkan/VulkanConstants.h" #include "vulkan/VulkanConstants.h"
@@ -20,6 +21,8 @@
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h" #include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
#include "vulkan/utils/Image.h" #include "vulkan/utils/Image.h"
#include "AndroidFrameCallback.h"
#include <private/backend/BackendUtilsAndroid.h> #include <private/backend/BackendUtilsAndroid.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
@@ -32,6 +35,7 @@
#include <android/hardware_buffer.h> #include <android/hardware_buffer.h>
#include <android/native_window.h> #include <android/native_window.h>
#include <new>
#include <utility> #include <utility>
using namespace bluevk; using namespace bluevk;
@@ -158,7 +162,55 @@ std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardware
}; };
} }
}// namespace uint32_t selectMemoryTypeForExternalImage(VkPhysicalDevice physicalDevice, VkDevice device,
VkImage image, uint32_t types, VkFlags requiredMemoryFlags) {
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
uint32_t const memoryTypeIndex =
VulkanContext::selectMemoryType(memoryProperties, types, requiredMemoryFlags);
if constexpr (FVK_RENDERDOC_CAPTURE_MODE) {
// RenderDoc will replay external resources as non-external.
// Adjust properties that will trip it up when replaying, even though these are not valid.
// Update memory type index if necessary so that we can replay the capture.
VkMemoryRequirements imageMemoryRequirements;
vkGetImageMemoryRequirements(device, image, &imageMemoryRequirements);
uint32_t const imageMemoryTypeBits = imageMemoryRequirements.memoryTypeBits;
bool const isMemoryTypeSupported = ((1 << memoryTypeIndex) & imageMemoryTypeBits) != 0;
if (isMemoryTypeSupported) {
return memoryTypeIndex;
}
// Current memory type will not be replayable by RenderDoc.
// Attempt to change the memory type index
VkMemoryPropertyFlags const kRenderDocFallBackReqs = 0;
uint32_t commonMemoryTypeBits = types & imageMemoryTypeBits;
uint32_t commonTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
commonMemoryTypeBits, kRenderDocFallBackReqs);
if (commonMemoryTypeBits && commonTypeIndex != VK_MAX_MEMORY_TYPES) {
return commonTypeIndex;
}
uint32_t imageTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
imageMemoryTypeBits, kRenderDocFallBackReqs);
if (imageTypeIndex != VK_MAX_MEMORY_TYPES) {
return imageTypeIndex;
}
}
return memoryTypeIndex;
}
} // namespace
// ---------------------------------------------------------------------------------------------
struct VulkanPlatformAndroid::AndroidDetails {
AndroidFrameCallback androidFrameCallback;
};
// ---------------------------------------------------------------------------------------------
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() { VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
if (__builtin_available(android 26, *)) { if (__builtin_available(android 26, *)) {
@@ -168,14 +220,19 @@ VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid()
} }
} }
VulkanPlatformAndroid::VulkanPlatformAndroid() { // ---------------------------------------------------------------------------------------------
VulkanPlatformAndroid::VulkanPlatformAndroid() :
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
mOSVersion = android_get_device_api_level(); mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) { if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__; mOSVersion = __ANDROID_API_FUTURE__;
} }
} }
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept = default; VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept {
delete &mAndroidDetails;
}
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage( Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
AHardwareBuffer const* buffer, bool sRGB) noexcept { AHardwareBuffer const* buffer, bool sRGB) noexcept {
@@ -354,8 +411,6 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
.image = image, .image = image,
.buffer = VK_NULL_HANDLE, .buffer = VK_NULL_HANDLE,
}; };
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
VkMemoryPropertyFlags requiredMemoryFlags = VkMemoryPropertyFlags requiredMemoryFlags =
!isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE) !isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
@@ -365,8 +420,8 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT; requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
} }
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties, uint32_t const memoryTypeIndex = selectMemoryTypeForExternalImage(physicalDevice, device,
metadata.memoryTypeBits, requiredMemoryFlags); image, metadata.memoryTypeBits, requiredMemoryFlags);
VkMemoryAllocateInfo const allocInfo = { VkMemoryAllocateInfo const allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
@@ -459,14 +514,14 @@ int VulkanPlatformAndroid::getOSVersion() const noexcept {
} }
void VulkanPlatformAndroid::terminate() { void VulkanPlatformAndroid::terminate() {
mAndroidFrameCallback.terminate(); mAndroidDetails.androidFrameCallback.terminate();
VulkanPlatform::terminate(); VulkanPlatform::terminate();
} }
Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) { Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) {
Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig); Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig);
if (driver) { if (driver) {
mAndroidFrameCallback.init(); mAndroidDetails.androidFrameCallback.init();
} }
return driver; return driver;
} }
@@ -482,13 +537,19 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
} }
AndroidFrameCallback::Timeline const preferredTimeline{ AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidFrameCallback.getPreferredTimeline() }; mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime; outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime; outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline; outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
// From this point on, we always return "success" because some timings were returned.
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain); auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming); vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
return true;
} }
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept { bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
@@ -502,13 +563,4 @@ bool VulkanPlatformAndroid::queryFrameTimestamps(SwapChain const* swapchain, uin
return vulkanSwapchain->queryFrameTimestamps(frameId, outFrameTimestamps); return vulkanSwapchain->queryFrameTimestamps(frameId, outFrameTimestamps);
} }
// Deprecated platform dependent helper methods
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept {
return SurfaceBundle{};
}
} // namespace filament::backend } // namespace filament::backend
@@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatformApple.h>
#include "vulkan/VulkanConstants.h" #include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanDriverFactory.h"
#include <utils/Panic.h> #include <utils/Panic.h>
@@ -36,15 +35,15 @@ using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { VulkanPlatform::ExtensionSet VulkanPlatformApple::getSwapchainInstanceExtensions() const {
ExtensionSet const ret = { ExtensionSet const ret = {
VK_EXT_METAL_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME,
}; };
return ret; return ret;
} }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow, VulkanPlatform::SurfaceBundle VulkanPlatformApple::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept { VkInstance instance, uint64_t flags) const noexcept {
VkSurfaceKHR surface; VkSurfaceKHR surface;
CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow; CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(mlayer) << "Unable to obtain Metal-backed layer."; FILAMENT_CHECK_POSTCONDITION(mlayer) << "Unable to obtain Metal-backed layer.";
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2023 The Android Open Source Project * Copyright (C) 2025 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatformLinux.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include "vulkan/VulkanConstants.h" #include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanDriverFactory.h"
#include <utils/Panic.h> #include <utils/Panic.h>
@@ -74,17 +73,13 @@
void* library = nullptr; void* library = nullptr;
} g_x11_vk; } g_x11_vk;
}// anonymous namespace }// anonymous namespace
#elif defined(WIN32)
// No platform specific includes
#else
// Not a supported Vulkan platform
#endif #endif
using namespace bluevk; using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { VulkanPlatform::ExtensionSet VulkanPlatformLinux::getSwapchainInstanceExtensions() const {
VulkanPlatform::ExtensionSet const ret = { VulkanPlatform::ExtensionSet const ret = {
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) #if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
@@ -95,15 +90,13 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
#if defined(FILAMENT_SUPPORTS_XLIB) #if defined(FILAMENT_SUPPORTS_XLIB)
VK_KHR_XLIB_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
#endif #endif
#elif defined(WIN32)
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
#endif #endif
}; };
return ret; return ret;
} }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow, VulkanPlatform::SurfaceBundle VulkanPlatformLinux::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept { VkInstance instance, uint64_t flags) const noexcept {
VkSurfaceKHR surface; VkSurfaceKHR surface;
// On certain platforms, the extent of the surface cannot be queried from Vulkan. In those // On certain platforms, the extent of the surface cannot be queried from Vulkan. In those
@@ -182,26 +175,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativ
<< "vkCreateXlibSurfaceKHR error=" << static_cast<int32_t>(result); << "vkCreateXlibSurfaceKHR error=" << static_cast<int32_t>(result);
} }
#endif #endif
#elif defined(WIN32)
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
// to that of the native window we've been given. This isn't a perfect solution, because an
// application could create swap chains on multiple native windows with varying DPI-awareness,
// but even then, at least one of the windows would be guaranteed to work correctly.
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
VkWin32SurfaceCreateInfoKHR const createInfo = {
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
.hinstance = GetModuleHandle(nullptr),
.hwnd = (HWND) nativeWindow,
};
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
(VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateWin32SurfaceKHR failed."
<< " error=" << static_cast<int32_t>(result);
#endif #endif
return std::make_tuple(surface, extent); return std::make_tuple(surface, extent);
} }
@@ -165,6 +165,11 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
VkResult VulkanPlatformSurfaceSwapChain::create() { VkResult VulkanPlatformSurfaceSwapChain::create() {
#ifdef __ANDROID__ #ifdef __ANDROID__
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true); NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(mNativeWindow), false);
}
#endif #endif
VkSurfaceFormatKHR surfaceFormat = {}; VkSurfaceFormatKHR surfaceFormat = {};
@@ -356,13 +361,15 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
CompositorTiming* outCompositorTiming) const { CompositorTiming* outCompositorTiming) const {
#ifdef __ANDROID__ #ifdef __ANDROID__
// fallback to private APIs // fallback to private APIs
int const status = NativeWindow::getCompositorTiming( if (UTILS_VERY_LIKELY(mNativeWindow)) {
static_cast<ANativeWindow*>(mNativeWindow), int const status = NativeWindow::getCompositorTiming(
&outCompositorTiming->compositeDeadline, static_cast<ANativeWindow*>(mNativeWindow),
&outCompositorTiming->compositeInterval, &outCompositorTiming->compositeDeadline,
&outCompositorTiming->compositeToPresentLatency); &outCompositorTiming->compositeInterval,
if (status == 0) { &outCompositorTiming->compositeToPresentLatency);
return true; if (status == 0) {
return true;
}
} }
#endif #endif
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming); return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
@@ -25,7 +25,8 @@
#include <bluevk/BlueVK.h> #include <bluevk/BlueVK.h>
#ifdef __ANDROID__ #ifdef __ANDROID__
#include <AndroidSwapChainHelper.h> #include "AndroidSwapChainHelper.h"
#include "AndroidNativeWindow.h"
#endif #endif
#include <unordered_map> #include <unordered_map>
@@ -122,6 +123,7 @@ private:
#ifdef __ANDROID__ #ifdef __ANDROID__
AndroidSwapChainHelper mImpl{}; AndroidSwapChainHelper mImpl{};
AndroidProducerThrottling mProducerThrottling;
#endif #endif
}; };
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2025 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 <backend/platforms/VulkanPlatformWindows.h>
#include <backend/DriverEnums.h>
#include "vulkan/VulkanConstants.h"
#include <utils/Panic.h>
#include <bluevk/BlueVK.h>
#include <tuple>
#include <stdint.h>
#include <stddef.h>
#if defined(WIN32)
#include <windows.h>
#endif
using namespace bluevk;
namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatformWindows::getSwapchainInstanceExtensions() const {
VulkanPlatform::ExtensionSet const ret = {
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
};
return ret;
}
VulkanPlatform::SurfaceBundle VulkanPlatformWindows::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) const noexcept {
VkSurfaceKHR surface;
VkExtent2D extent;
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
// to that of the native window we've been given. This isn't a perfect solution, because an
// application could create swap chains on multiple native windows with varying DPI-awareness,
// but even then, at least one of the windows would be guaranteed to work correctly.
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
VkWin32SurfaceCreateInfoKHR const createInfo = {
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
.hinstance = GetModuleHandle(nullptr),
.hwnd = (HWND) nativeWindow,
};
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
(VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateWin32SurfaceKHR failed."
<< " error=" << static_cast<int32_t>(result);
return std::make_tuple(surface, VkExtent2D{});
}
} // namespace filament::backend
+11 -11
View File
@@ -243,10 +243,10 @@ public:
/** Utility to set the projection matrix from the field-of-view. /** Utility to set the projection matrix from the field-of-view.
* *
* @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180. * @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180.
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far > \p near.
* @param direction direction of the \p fovInDegrees parameter. * @param direction direction of the \p fovInDegrees parameter.
* *
* @see Fov. * @see Fov.
*/ */
@@ -256,9 +256,9 @@ public:
/** Utility to set the projection matrix from the focal length. /** Utility to set the projection matrix from the focal length.
* *
* @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0. * @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0.
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far > \p near.
*/ */
void setLensProjection(double focalLengthInMillimeters, void setLensProjection(double focalLengthInMillimeters,
double aspect, double near, double far); double aspect, double near, double far);
@@ -270,8 +270,8 @@ public:
* that is all 3 axis are mapped to [-1, 1]. * that is all 3 axis are mapped to [-1, 1].
* *
* @param projection custom projection matrix used for rendering and culling * @param projection custom projection matrix used for rendering and culling
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far != \p near.
*/ */
void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept; void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept;
@@ -282,8 +282,8 @@ public:
* *
* @param projection custom projection matrix used for rendering * @param projection custom projection matrix used for rendering
* @param projectionForCulling custom projection matrix used for culling * @param projectionForCulling custom projection matrix used for culling
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far != \p near.
*/ */
void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling,
double near, double far) noexcept; double near, double far) noexcept;
+6
View File
@@ -113,8 +113,10 @@ public:
/** /**
* Retrieve a history of frame timing information. The maximum frame history size is * Retrieve a history of frame timing information. The maximum frame history size is
* given by getMaxFrameHistorySize(). * given by getMaxFrameHistorySize().
* All or part of the history can be lost when using a different SwapChain in beginFrame().
* @param historySize requested history size. The returned vector could be smaller. * @param historySize requested history size. The returned vector could be smaller.
* @return A vector of FrameInfo. * @return A vector of FrameInfo.
* @see beginFrame()
*/ */
utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory( utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory(
size_t historySize = 1) const noexcept; size_t historySize = 1) const noexcept;
@@ -326,6 +328,8 @@ public:
* or 0 if unknown. This value should be the timestamp of * or 0 if unknown. This value should be the timestamp of
* the last h/w vsync. It is expressed in the * the last h/w vsync. It is expressed in the
* std::chrono::steady_clock time base. * std::chrono::steady_clock time base.
* On Android this should be the frame time received from
* a Choreographer.
* @param swapChain A pointer to the SwapChain instance to use. * @param swapChain A pointer to the SwapChain instance to use.
* *
* @return * @return
@@ -337,6 +341,8 @@ public:
* *
* @note * @note
* All calls to render() must happen *after* beginFrame(). * All calls to render() must happen *after* beginFrame().
* It is recommended to use the same swapChain for every call to beginFrame, failing to do
* so can result is losing all or part of the FrameInfo history.
* *
* @see * @see
* endFrame() * endFrame()
+8
View File
@@ -541,6 +541,14 @@ public:
*/ */
DynamicResolutionOptions getDynamicResolutionOptions() const noexcept; DynamicResolutionOptions getDynamicResolutionOptions() const noexcept;
/**
* Returns the last dynamic resolution scale factor used by this view. This value is updated
* when Renderer::render(View*) is called
* @return a float2 where x is the horizontal and y the vertical scale factor.
* @see Renderer::render
*/
math::float2 getLastDynamicResolutionScale() const noexcept;
/** /**
* Sets the rendering quality for this view. Refer to RenderQuality for more * Sets the rendering quality for this view. Refer to RenderQuality for more
* information about the different settings available. * information about the different settings available.
+28 -5
View File
@@ -16,14 +16,30 @@
#include "details/Camera.h" #include "details/Camera.h"
#include <filament/Camera.h>
#include <math/mat4.h> #include <math/mat4.h>
#include <utils/Panic.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <cstddef>
#include <cstdint>
namespace filament { namespace filament {
using namespace math; using namespace math;
void Camera::setProjection(double const fovInDegrees, double const aspect, double const near, double const far, void Camera::setProjection(double const fovInDegrees, double const aspect,
Fov const direction) { double const near, double const far, Fov const direction) {
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
<< "Camera preconditions not met in setProjection(): near <= 0 or far <= near, near="
<< near << ", far=" << far;
setCustomProjection( setCustomProjection(
projection(direction, fovInDegrees, aspect, near), projection(direction, fovInDegrees, aspect, near),
projection(direction, fovInDegrees, aspect, near, far), projection(direction, fovInDegrees, aspect, near, far),
@@ -32,6 +48,11 @@ void Camera::setProjection(double const fovInDegrees, double const aspect, doubl
void Camera::setLensProjection(double const focalLengthInMillimeters, void Camera::setLensProjection(double const focalLengthInMillimeters,
double const aspect, double const near, double const far) { double const aspect, double const near, double const far) {
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
<< "Camera preconditions not met in setLensProjection(): near <= 0 or far <= near, near="
<< near << ", far=" << far;
setCustomProjection( setCustomProjection(
projection(focalLengthInMillimeters, aspect, near), projection(focalLengthInMillimeters, aspect, near),
projection(focalLengthInMillimeters, aspect, near, far), projection(focalLengthInMillimeters, aspect, near, far),
@@ -149,8 +170,9 @@ utils::Entity Camera::getEntity() const noexcept {
return downcast(this)->getEntity(); return downcast(this)->getEntity();
} }
void Camera::setExposure(float const aperture, float const shutterSpeed, float const ISO) noexcept { void Camera::setExposure(
downcast(this)->setExposure(aperture, shutterSpeed, ISO); float const aperture, float const shutterSpeed, float const sensitivity) noexcept {
downcast(this)->setExposure(aperture, shutterSpeed, sensitivity);
} }
float Camera::getAperture() const noexcept { float Camera::getAperture() const noexcept {
@@ -177,7 +199,8 @@ double Camera::getFocalLength() const noexcept {
return downcast(this)->getFocalLength(); return downcast(this)->getFocalLength();
} }
double Camera::computeEffectiveFocalLength(double const focalLength, double const focusDistance) noexcept { double Camera::computeEffectiveFocalLength(
double const focalLength, double const focusDistance) noexcept {
return FCamera::computeEffectiveFocalLength(focalLength, focusDistance); return FCamera::computeEffectiveFocalLength(focalLength, focusDistance);
} }
+24 -2
View File
@@ -226,6 +226,8 @@ bool Froxelizer::prepare(
filament::Viewport const& viewport, filament::Viewport const& viewport,
const mat4f& projection, float const projectionNear, float const projectionFar, const mat4f& projection, float const projectionNear, float const projectionFar,
float4 const& clipTransform) noexcept { float4 const& clipTransform) noexcept {
assert_invariant(projectionFar > projectionNear);
assert_invariant(projectionNear > 0);
setViewport(viewport); setViewport(viewport);
setProjection(projection, projectionNear, projectionFar); setProjection(projection, projectionNear, projectionFar);
@@ -383,8 +385,28 @@ bool Froxelizer::update() noexcept {
bool uniformsNeedUpdating = false; bool uniformsNeedUpdating = false;
if (UTILS_UNLIKELY(mDirtyFlags & (OPTIONS_CHANGED|PROJECTION_CHANGED))) { if (UTILS_UNLIKELY(mDirtyFlags & (OPTIONS_CHANGED|PROJECTION_CHANGED))) {
float const zLightFar = clamp(mUserZLightFar, mNear, mFar);
float zLightNear = clamp(mUserZLightNear, mNear, mFar); // sanitize the user's near/far
float zLightNear = mUserZLightNear;
float zLightFar = mUserZLightFar;
if (zLightFar == zLightNear) {
zLightNear = mNear;
zLightFar = mFar;
}
if (zLightFar < zLightNear) {
std::swap(zLightFar, zLightNear);
}
if (zLightNear < mNear || zLightNear >= mFar) {
zLightNear = mNear;
}
if (zLightFar > mFar || zLightFar <= mNear) {
zLightFar = mFar;
}
assert_invariant(zLightNear < zLightFar);
assert_invariant(zLightNear >= mNear && zLightNear <= mFar);
assert_invariant(zLightFar <= mFar && zLightNear >= mNear);
zLightNear = std::min(zLightNear, zLightFar); zLightNear = std::min(zLightNear, zLightFar);
if (zLightFar != mZLightFar || zLightNear != mZLightNear) { if (zLightFar != mZLightFar || zLightNear != mZLightNear) {
mDirtyFlags |= VIEWPORT_CHANGED; mDirtyFlags |= VIEWPORT_CHANGED;
+23 -19
View File
@@ -258,6 +258,10 @@ void PostProcessManager::bindPerRenderableDescriptorSet(DriverApi& driver) const
{ { 0, 0 }, driver }); { { 0, 0 }, driver });
} }
UboManager* PostProcessManager::getUboManager() const noexcept {
return mEngine.getUboManager();
}
UTILS_NOINLINE UTILS_NOINLINE
void PostProcessManager::registerPostProcessMaterial(std::string_view const name, void PostProcessManager::registerPostProcessMaterial(std::string_view const name,
StaticMaterialInfo const& info) { StaticMaterialInfo const& info) {
@@ -513,7 +517,7 @@ UTILS_NOINLINE
void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver, void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver,
FrameGraphResources::RenderPassInfo const& out, FMaterialInstance const* mi, FrameGraphResources::RenderPassInfo const& out, FMaterialInstance const* mi,
PostProcessVariant const variant) const noexcept { PostProcessVariant const variant) const noexcept {
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
FMaterial const* const ma = mi->getMaterial(); FMaterial const* const ma = mi->getMaterial();
PipelineState const pipeline = getPipelineState(ma, variant); PipelineState const pipeline = getPipelineState(ma, variant);
@@ -645,7 +649,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
auto th = driver.createTextureView(in, level, 1); auto th = driver.createTextureView(in, level, 1);
mi->setParameter("depth", th, SamplerParams{ mi->setParameter("depth", th, SamplerParams{
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuad(out, pipeline, driver); renderFullScreenQuad(out, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL); DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
@@ -1079,7 +1083,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
mi->setParameter("ssctRayCount", mi->setParameter("ssctRayCount",
float2{ options.ssct.rayCount, 1.0f / float(options.ssct.rayCount) }); float2{ options.ssct.rayCount, 1.0f / float(options.ssct.rayCount) });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -1200,7 +1204,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
mi->setParameter("sampleCount", kGaussianCount); mi->setParameter("sampleCount", kGaussianCount);
mi->setParameter("farPlaneOverEdgeDistance", -zf / config.bilateralThreshold); mi->setParameter("farPlaneOverEdgeDistance", -zf / config.bilateralThreshold);
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -1895,7 +1899,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres? mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres?
mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h }); mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuad(out, pipeline, driver); renderFullScreenQuad(out, pipeline, driver);
@@ -2425,7 +2429,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
mi->setParameter("source", hwOutView, SamplerParams{ mi->setParameter("source", hwOutView, SamplerParams{
.filterMag = SamplerMagFilter::LINEAR, .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST});
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuad(hwDstRT, pipeline, driver); renderFullScreenQuad(hwDstRT, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL); DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
@@ -2533,7 +2537,7 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
FMaterialInstance* const mi = FMaterialInstance* const mi =
configureColorGradingMaterial(material, colorGrading, colorGradingConfig, configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
vignetteOptions, width, height); vignetteOptions, width, height);
mi->commit(driver); mi->commit(driver, getUboManager());
} }
void PostProcessManager::colorGradingSubpass(DriverApi& driver, void PostProcessManager::colorGradingSubpass(DriverApi& driver,
@@ -2566,7 +2570,7 @@ void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomRe
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma); auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.customResolve = fixedIndex; mFixedMaterialInstanceIndex.customResolve = fixedIndex;
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f), mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
mi->commit(driver); mi->commit(driver, getUboManager());
material.getMaterial(mEngine); material.getMaterial(mEngine);
} }
@@ -2619,7 +2623,7 @@ void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver) noexcep
auto ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE); auto ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma); auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.clearDepth = fixedIndex; mFixedMaterialInstanceIndex.clearDepth = fixedIndex;
mi->commit(driver); mi->commit(driver, getUboManager());
material.getMaterial(mEngine); material.getMaterial(mEngine);
} }
@@ -3111,7 +3115,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
mat4f{ historyProjection * inverse(current.projection) } * mat4f{ historyProjection * inverse(current.projection) } *
normalizedToClip); normalizedToClip);
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
if (colorGradingConfig.asSubpass) { if (colorGradingConfig.asSubpass) {
@@ -3196,7 +3200,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
mi->setParameter("resolution", float4{ mi->setParameter("resolution", float4{
outputDesc.width, outputDesc.height, outputDesc.width, outputDesc.height,
1.0f / outputDesc.width, 1.0f / outputDesc.height }); 1.0f / outputDesc.width, 1.0f / outputDesc.height });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(material.getMaterial(mEngine), variant); auto pipeline = getPipelineState(material.getMaterial(mEngine), variant);
@@ -3287,7 +3291,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
float(vp.width) / inputDesc.width, float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height float(vp.height) / inputDesc.height
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto out = resources.getRenderPassInfo(); auto out = resources.getRenderPassInfo();
@@ -3374,7 +3378,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleSGSR1(FrameGraph& fg,
float(inputDesc.height) float(inputDesc.height)
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto const out = resources.getRenderPassInfo(); auto const out = resources.getRenderPassInfo();
@@ -3469,7 +3473,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
mi->setParameter("resolution", mi->setParameter("resolution",
float4{ outputDesc.width, outputDesc.height, float4{ outputDesc.width, outputDesc.height,
1.0f / outputDesc.width, 1.0f / outputDesc.height }); 1.0f / outputDesc.width, 1.0f / outputDesc.height });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
} }
@@ -3496,7 +3500,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
float(vp.width) / inputDesc.width, float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height float(vp.height) / inputDesc.height
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
} }
@@ -3582,7 +3586,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
if (layer) { if (layer) {
mi->setParameter("layerIndex", layer); mi->setParameter("layerIndex", layer);
} }
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -3840,7 +3844,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
}); });
mi->setParameter("layer", uint32_t(layer)); mi->setParameter("layer", uint32_t(layer));
mi->setParameter("uvscale", 1.0f / float(dim)); mi->setParameter("uvscale", 1.0f / float(dim));
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuadWithScissor(out, pipeline, scissor, driver); renderFullScreenQuadWithScissor(out, pipeline, scissor, driver);
@@ -3942,7 +3946,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
float(vp.width) / inputDesc.width, float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height float(vp.height) / inputDesc.height
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -3959,7 +3963,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
// Render all layers of the texture to the screen side-by-side. // Render all layers of the texture to the screen side-by-side.
for (uint32_t i = 0; i < inputTextureDesc.depth; ++i) { for (uint32_t i = 0; i < inputTextureDesc.depth; ++i) {
mi->setParameter("layerIndex", i); mi->setParameter("layerIndex", i);
mi->commit(driver); mi->commit(driver, getUboManager());
renderFullScreenQuad(out, pipeline, driver); renderFullScreenQuad(out, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL); DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
// From the second draw, don't clear the targetbuffer. // From the second draw, don't clear the targetbuffer.
+3
View File
@@ -65,6 +65,7 @@ class FMaterialInstance;
class FrameGraph; class FrameGraph;
class RenderPass; class RenderPass;
class RenderPassBuilder; class RenderPassBuilder;
class UboManager;
struct CameraInfo; struct CameraInfo;
class PostProcessManager { class PostProcessManager {
@@ -422,6 +423,8 @@ private:
return getMaterialInstance(ma); return getMaterialInstance(ma);
} }
UboManager* getUboManager() const noexcept;
backend::RenderPrimitiveHandle mFullScreenQuadRph; backend::RenderPrimitiveHandle mFullScreenQuadRph;
backend::VertexBufferInfoHandle mFullScreenQuadVbih; backend::VertexBufferInfoHandle mFullScreenQuadVbih;
backend::DescriptorSetLayoutHandle mPerRenderableDslh; backend::DescriptorSetLayoutHandle mPerRenderableDslh;
+25 -15
View File
@@ -1012,6 +1012,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
uint32_t const index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT; uint32_t const index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT;
assert_invariant(index < mCustomCommands.size()); assert_invariant(index < mCustomCommands.size());
pCustomCommands[index](); pCustomCommands[index]();
currentPipeline = {};
currentPrimitiveHandle ={};
continue; continue;
} }
@@ -1069,26 +1071,34 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] = pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] =
ma->getDescriptorSetLayout(info.materialVariant).getHandle(); ma->getDescriptorSetLayout(info.materialVariant).getHandle();
// If we have a ColorPassDescriptorSet we use it to bind the per-view
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) { // descriptor-set (ideally only if it changed).
// It is possible to get a post-process material here (even though it's // If we don't, it means the descriptor-set is already bound and the layout we
// not technically a public API yet, it is used by the IBLPrefilterLibrary. // got from the material above should match. This is the case for situations
// Ideally we would have a more formal compute API). In this case, we need // where we have a known per-view descriptor-set layout,
// to set the post-process descriptor-set. // e.g.: postfx, shadow-maps, ssr and structure passes.
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver); if (mColorPassDescriptorSet) {
} else { if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
// If we have a ColorPassDescriptorSet we use it to bind the per-view // It is possible to get a post-process material here (even though it's
// descriptor-set (ideally only if it changed). If we don't, it means // not technically a public API yet, it is used by the IBLPrefilterLibrary.
// the descriptor-set is already bound and the layout we got from the // Ideally we would have a more formal compute API). In this case, we need
// material above should match. This is the case for situations where we // to set the post-process descriptor-set.
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
// structure passes. } else {
if (mColorPassDescriptorSet) {
// We have a ColorPassDescriptorSet, we need to go through it for binding // We have a ColorPassDescriptorSet, we need to go through it for binding
// the per-view descriptor-set because its layout can change based on the // the per-view descriptor-set because its layout can change based on the
// material. // material.
mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex()); mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex());
} }
} else {
// if we're here it means the per-view descriptor set is constant and
// already set. This will be the case for postfx, ssr, structure and
// shadow passes. All these passes use a static descriptor set layout
// (albeit potentially different for each pass). In particular the
// per-view UBO must be compatible for all material domains.
// This is the case by construction for postfx, ssr. However, shadows
// and structure have their own UBO, but it's content is (must be)
// compatible with POST_PROCESS and COMPUTE materials.
} }
// Each MaterialInstance has its own descriptor set. This binds it. // Each MaterialInstance has its own descriptor set. This binds it.
+4
View File
@@ -178,6 +178,10 @@ View::DynamicResolutionOptions View::getDynamicResolutionOptions() const noexcep
return downcast(this)->getDynamicResolutionOptions(); return downcast(this)->getDynamicResolutionOptions();
} }
math::float2 View::getLastDynamicResolutionScale() const noexcept {
return downcast(this)->getLastDynamicResolutionScale();
}
void View::setRenderQuality(const RenderQuality& renderQuality) noexcept { void View::setRenderQuality(const RenderQuality& renderQuality) noexcept {
downcast(this)->setRenderQuality(renderQuality); downcast(this)->setRenderQuality(renderQuality);
} }
+17 -2
View File
@@ -16,6 +16,7 @@
#include "details/BufferAllocator.h" #include "details/BufferAllocator.h"
#include <private/utils/Tracing.h>
#include <utils/Panic.h> #include <utils/Panic.h>
#include <utils/debug.h> #include <utils/debug.h>
@@ -142,7 +143,11 @@ void BufferAllocator::retire(AllocationId id) {
InternalSlotNode* targetNode = getNodeById(id); InternalSlotNode* targetNode = getNodeById(id);
assert_invariant(targetNode != nullptr); assert_invariant(targetNode != nullptr);
targetNode->slot.isAllocated = false; Slot& slot = targetNode->slot;
slot.isAllocated = false;
if (slot.gpuUseCount == 0) {
mHasPendingFrees = true;
}
} }
void BufferAllocator::acquireGpu(AllocationId id) { void BufferAllocator::acquireGpu(AllocationId id) {
@@ -157,10 +162,19 @@ void BufferAllocator::releaseGpu(AllocationId id) {
assert_invariant(targetNode != nullptr); assert_invariant(targetNode != nullptr);
assert_invariant(targetNode->slot.gpuUseCount > 0); assert_invariant(targetNode->slot.gpuUseCount > 0);
targetNode->slot.gpuUseCount--; Slot& slot = targetNode->slot;
slot.gpuUseCount--;
if (slot.gpuUseCount == 0 && !slot.isAllocated) {
mHasPendingFrees = true;
}
} }
void BufferAllocator::releaseFreeSlots() { void BufferAllocator::releaseFreeSlots() {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
if (!mHasPendingFrees) {
return;
}
auto curr = mSlotPool.begin(); auto curr = mSlotPool.begin();
while (curr != mSlotPool.end()) { while (curr != mSlotPool.end()) {
if (!curr->slot.isFree()) { if (!curr->slot.isFree()) {
@@ -199,6 +213,7 @@ void BufferAllocator::releaseFreeSlots() {
curr = next; curr = next;
} }
mHasPendingFrees = false;
} }
BufferAllocator::allocation_size_t BufferAllocator::getTotalSize() const noexcept { BufferAllocator::allocation_size_t BufferAllocator::getTotalSize() const noexcept {
+2 -1
View File
@@ -113,8 +113,9 @@ private:
[[nodiscard]] InternalSlotNode* getNodeById(AllocationId id) const noexcept; [[nodiscard]] InternalSlotNode* getNodeById(AllocationId id) const noexcept;
bool mHasPendingFrees = false;
allocation_size_t mTotalSize; allocation_size_t mTotalSize;
const allocation_size_t mSlotSize; // Size of a single slot in bytes. const allocation_size_t mSlotSize; // Size of a single slot in bytes
std::list<InternalSlotNode> mSlotPool; // All slots, including both allocated and freed std::list<InternalSlotNode> mSlotPool; // All slots, including both allocated and freed
std::multimap</*slot size*/allocation_size_t, InternalSlotNode*> mFreeList; std::multimap</*slot size*/allocation_size_t, InternalSlotNode*> mFreeList;
std::unordered_map</*slot offset*/allocation_size_t, InternalSlotNode*> mOffsetMap; std::unordered_map</*slot offset*/allocation_size_t, InternalSlotNode*> mOffsetMap;
+31 -14
View File
@@ -24,27 +24,35 @@
#include <filament/Camera.h> #include <filament/Camera.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Panic.h> #include <utils/Panic.h>
#include <math/scalar.h> #include <math/scalar.h>
#include <math/mat4.h>
#include <math/vec2.h> #include <math/vec2.h>
#include <math/vec3.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <limits>
using namespace filament::math; using namespace filament::math;
using namespace utils; using namespace utils;
namespace filament { namespace filament {
static constexpr const float MIN_APERTURE = 0.5f; static constexpr float MIN_APERTURE = 0.5f;
static constexpr const float MAX_APERTURE = 64.0f; static constexpr float MAX_APERTURE = 64.0f;
static constexpr const float MIN_SHUTTER_SPEED = 1.0f / 25000.0f; static constexpr float MIN_SHUTTER_SPEED = 1.0f / 25000.0f;
static constexpr const float MAX_SHUTTER_SPEED = 60.0f; static constexpr float MAX_SHUTTER_SPEED = 60.0f;
static constexpr const float MIN_SENSITIVITY = 10.0f; static constexpr float MIN_SENSITIVITY = 10.0f;
static constexpr const float MAX_SENSITIVITY = 204800.0f; static constexpr float MAX_SENSITIVITY = 204800.0f;
FCamera::FCamera(FEngine& engine, Entity const e) FCamera::FCamera(FEngine& engine, Entity const e)
: mEngine(engine), : mEngine(engine),
mEntity(e) { mEntity(e) {
setProjection(Projection::PERSPECTIVE, -1.0, 1.0, -1.0, 1.0, 0.1, 1.0);
} }
mat4 FCamera::projection(Fov const direction, double const fovInDegrees, mat4 FCamera::projection(Fov const direction, double const fovInDegrees,
@@ -84,12 +92,16 @@ mat4 FCamera::projection(double const focalLengthInMillimeters,
* All methods for setting the projection funnel through here * All methods for setting the projection funnel through here
*/ */
void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p, void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& projection,
mat4 const& c, double const near, double const far) noexcept { mat4 const& projectionForCulling, double const near, double const far) noexcept {
FILAMENT_CHECK_PRECONDITION(near != far)
<< "Camera preconditions not met in setCustomProjection(): near = far = " << near;
for (auto& eyeProjection: mEyeProjection) { for (auto& eyeProjection: mEyeProjection) {
eyeProjection = p; eyeProjection = projection;
} }
mProjectionForCulling = c; mProjectionForCulling = projectionForCulling;
mNear = near; mNear = near;
mFar = far; mFar = far;
} }
@@ -97,10 +109,15 @@ void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p,
void UTILS_NOINLINE FCamera::setCustomEyeProjection(mat4 const* projection, size_t const count, void UTILS_NOINLINE FCamera::setCustomEyeProjection(mat4 const* projection, size_t const count,
mat4 const& projectionForCulling, double const near, double const far) { mat4 const& projectionForCulling, double const near, double const far) {
const Engine::Config& config = mEngine.getConfig(); const Engine::Config& config = mEngine.getConfig();
FILAMENT_CHECK_PRECONDITION(near != far)
<< "Camera preconditions not met in setCustomEyeProjection(): near = far = " << near;
FILAMENT_CHECK_PRECONDITION(count >= config.stereoscopicEyeCount) FILAMENT_CHECK_PRECONDITION(count >= config.stereoscopicEyeCount)
<< "All eye projections must be supplied together, count must be >= " << "All eye projections must be supplied together, count must be >= "
"config.stereoscopicEyeCount (" "config.stereoscopicEyeCount ("
<< config.stereoscopicEyeCount << ")"; << config.stereoscopicEyeCount << ")";
for (int i = 0; i < config.stereoscopicEyeCount; i++) { for (int i = 0; i < config.stereoscopicEyeCount; i++) {
mEyeProjection[i] = projection[i]; mEyeProjection[i] = projection[i];
} }
@@ -290,10 +307,10 @@ CameraInfo::CameraInfo(FCamera const& camera,
model = mat4f{ modelMatrix }; model = mat4f{ modelMatrix };
view = mat4f{ inverse(modelMatrix) }; view = mat4f{ inverse(modelMatrix) };
worldTransform = inWorldTransform; worldTransform = inWorldTransform;
zn = (float)camera.getNear(); zn = float(camera.getNear());
zf = (float)camera.getCullingFar(); zf = float(camera.getCullingFar());
ev100 = Exposure::ev100(camera); ev100 = Exposure::ev100(camera);
f = (float)camera.getFocalLength(); f = float(camera.getFocalLength());
A = f / camera.getAperture(); A = f / camera.getAperture();
d = std::max(zn, camera.getFocusDistance()); d = std::max(zn, camera.getFocusDistance());
} }
+47 -6
View File
@@ -468,11 +468,26 @@ void FEngine::init() {
} }
mDefaultMaterial = downcast(defaultMaterialBuilder.build(*this)); mDefaultMaterial = downcast(defaultMaterialBuilder.build(*this));
} }
// We must commit the default material instance here. It may not be used in a scene, but its // We must commit the default material instance here. It may not be used in a scene, but its
// descriptor set may still be used for shared variants. // descriptor set may still be used for shared variants.
mDefaultMaterial->getDefaultInstance()->commit(driverApi); //
// Note that this material instance is instantiated before the creation of UboManager, so at
// this point `isUboBatchingEnabled` is `false`, and it will fall back to individual UBO
// automatically.
mDefaultMaterial->getDefaultInstance()->commit(driverApi, mUboManager);
if (UTILS_UNLIKELY(getSupportedFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1)) { if (UTILS_UNLIKELY(getSupportedFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1)) {
// UBO batching is not supported in feature level 0
if (features.material.enable_material_instance_uniform_batching) {
// Ubo size of each material instance is at least 16 bytes.
constexpr BufferAllocator::allocation_size_t minSlotSize = 16;
auto const uboOffsetAlignment = static_cast<BufferAllocator::allocation_size_t>(
driverApi.getUniformBufferOffsetAlignment());
BufferAllocator::allocation_size_t slotSize = std::max(minSlotSize, uboOffsetAlignment);
mUboManager = new UboManager(getDriverApi(), slotSize, mConfig.sharedUboInitialSizeInBytes);
}
mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this)); mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this));
constexpr float3 dummyPositions[1] = {}; constexpr float3 dummyPositions[1] = {};
@@ -653,6 +668,12 @@ void FEngine::shutdown() {
driver.destroyRenderTarget(std::move(mDefaultRenderTarget)); driver.destroyRenderTarget(std::move(mDefaultRenderTarget));
if (isUboBatchingEnabled()) {
mUboManager->terminate(driver);
delete mUboManager;
mUboManager = nullptr;
}
/* /*
* Shutdown the backend... * Shutdown the backend...
*/ */
@@ -693,18 +714,30 @@ void FEngine::prepare() {
// UBOs that are visible only. It's not such a big issue because the actual upload() is // UBOs that are visible only. It's not such a big issue because the actual upload() is
// skipped if the UBO hasn't changed. Still we could have a lot of these. // skipped if the UBO hasn't changed. Still we could have a lot of these.
DriverApi& driver = getDriverApi(); DriverApi& driver = getDriverApi();
const bool useUboBatching = isUboBatchingEnabled();
if (useUboBatching) {
assert_invariant(mUboManager != nullptr);
mUboManager->beginFrame(driver);
}
UboManager* uboManager = mUboManager;
for (auto& materialInstanceList: mMaterialInstances) { for (auto& materialInstanceList: mMaterialInstances) {
materialInstanceList.second.forEach([&driver](FMaterialInstance* item) { materialInstanceList.second.forEach([&driver, uboManager](FMaterialInstance* item) {
// post-process materials instances must be commited explicitly because their // post-process materials instances must be commited explicitly because their
// parameters are typically not set at this point in time. // parameters are typically not set at this point in time.
if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) { if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) {
item->commitStreamUniformAssociations(driver); item->commitStreamUniformAssociations(driver);
item->commit(driver); item->commit(driver, uboManager);
} }
}); });
} }
if (useUboBatching) {
assert_invariant(mUboManager != nullptr);
getUboManager()->finishBeginFrame(getDriverApi());
}
mMaterials.forEach([](FMaterial* material) { mMaterials.forEach([](FMaterial* material) {
#if FILAMENT_ENABLE_MATDBG // NOLINT(*-include-cleaner) #if FILAMENT_ENABLE_MATDBG // NOLINT(*-include-cleaner)
material->checkProgramEdits(); material->checkProgramEdits();
@@ -721,6 +754,13 @@ void FEngine::gc() {
mCameraManager.gc(*this, em); mCameraManager.gc(*this, em);
} }
void FEngine::submitFrame() {
if (isUboBatchingEnabled()) {
DriverApi& driver = getDriverApi();
getUboManager()->endFrame(driver);
}
}
void FEngine::flush() { void FEngine::flush() {
// flush the command buffer // flush the command buffer
flushCommandBuffer(mCommandBufferQueue); flushCommandBuffer(mCommandBufferQueue);
@@ -951,9 +991,10 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
return p; return p;
} }
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name,
const char* name) noexcept { UboBatchingMode batchingMode) noexcept {
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name); FMaterialInstance* p =
mHeapAllocator.make<FMaterialInstance>(*this, material, name, batchingMode);
if (UTILS_LIKELY(p)) { if (UTILS_LIKELY(p)) {
auto pos = mMaterialInstances.emplace(material, "MaterialInstance"); auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
pos.first->second.insert(p); pos.first->second.insert(p);
+26 -5
View File
@@ -21,16 +21,17 @@
#include "Allocators.h" #include "Allocators.h"
#include "DFG.h" #include "DFG.h"
#include "PostProcessManager.h"
#include "ResourceList.h"
#include "HwDescriptorSetLayoutFactory.h" #include "HwDescriptorSetLayoutFactory.h"
#include "HwVertexBufferInfoFactory.h" #include "HwVertexBufferInfoFactory.h"
#include "MaterialCache.h" #include "MaterialCache.h"
#include "PostProcessManager.h"
#include "ResourceList.h"
#include "UboManager.h"
#include "components/CameraManager.h" #include "components/CameraManager.h"
#include "components/LightManager.h" #include "components/LightManager.h"
#include "components/TransformManager.h"
#include "components/RenderableManager.h" #include "components/RenderableManager.h"
#include "components/TransformManager.h"
#include "ds/DescriptorSetLayout.h" #include "ds/DescriptorSetLayout.h"
@@ -263,6 +264,14 @@ public:
return mBackend; return mBackend;
} }
bool isUboBatchingEnabled() const noexcept {
return mUboManager != nullptr;
}
UboManager* getUboManager() noexcept {
return mUboManager;
}
Platform* getPlatform() const noexcept { Platform* getPlatform() const noexcept {
return mPlatform; return mPlatform;
} }
@@ -336,11 +345,21 @@ public:
FRenderer* createRenderer() noexcept; FRenderer* createRenderer() noexcept;
// Defines whether a material instance should use UBO batching or not.
enum class UboBatchingMode {
// For default, it follows the engine settings.
// If UBO batching is enabled on the engine and the material domain is not SURFACE, it
// turns on the UBO batching. Otherwise, it turns off the UBO batching.
DEFAULT,
NO_UBO_BATCHING,
UBO_BATCHING
};
FMaterialInstance* createMaterialInstance(const FMaterial* material, FMaterialInstance* createMaterialInstance(const FMaterial* material,
const FMaterialInstance* other, const char* name) noexcept; const FMaterialInstance* other, const char* name) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material, FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name,
const char* name) noexcept; UboBatchingMode batchingMode) noexcept;
FScene* createScene() noexcept; FScene* createScene() noexcept;
FView* createView() noexcept; FView* createView() noexcept;
@@ -446,6 +465,7 @@ public:
void prepare(); void prepare();
void gc(); void gc();
void submitFrame();
using ShaderContent = utils::FixedCapacityVector<uint8_t>; using ShaderContent = utils::FixedCapacityVector<uint8_t>;
@@ -650,6 +670,7 @@ private:
uint32_t mFlushCounter = 0; uint32_t mFlushCounter = 0;
UboManager* mUboManager = nullptr;
RootArenaScope::Arena mPerRenderPassArena; RootArenaScope::Arena mPerRenderPassArena;
HeapAllocatorArena mHeapAllocator; HeapAllocatorArena mHeapAllocator;
+13 -11
View File
@@ -73,6 +73,7 @@ namespace filament {
using namespace backend; using namespace backend;
using namespace filaflat; using namespace filaflat;
using namespace utils; using namespace utils;
using UboBatchingMode = FEngine::UboBatchingMode;
struct Material::BuilderDetails { struct Material::BuilderDetails {
const void* mPayload = nullptr; const void* mPayload = nullptr;
@@ -217,16 +218,17 @@ void FMaterial::terminate(FEngine& engine) {
filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout( filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept { Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
if (Variant::isValidDepthVariant(variant)) { if (mDefinition.materialDomain == MaterialDomain::SURFACE) {
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE); // `variant` is only sensical for MaterialDomain::SURFACE
return mEngine.getPerViewDescriptorSetLayoutDepthVariant(); if (Variant::isValidDepthVariant(variant)) {
} return mEngine.getPerViewDescriptorSetLayoutDepthVariant();
if (Variant::isSSRVariant(variant)) { }
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE); if (Variant::isSSRVariant(variant)) {
return mEngine.getPerViewDescriptorSetLayoutSsrVariant(); return mEngine.getPerViewDescriptorSetLayoutSsrVariant();
}
} }
// mDefinition.perViewDescriptorSetLayout{Vsm} is already resolved for MaterialDomain
if (useVsmDescriptorSetLayout) { if (useVsmDescriptorSetLayout) {
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
return mDefinition.perViewDescriptorSetLayoutVsm; return mDefinition.perViewDescriptorSetLayoutVsm;
} }
return mDefinition.perViewDescriptorSetLayout; return mDefinition.perViewDescriptorSetLayout;
@@ -280,14 +282,14 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
return FMaterialInstance::duplicate(mDefaultMaterialInstance, name); return FMaterialInstance::duplicate(mDefaultMaterialInstance, name);
} else { } else {
// but if we don't, just create an instance with all the default parameters // but if we don't, just create an instance with all the default parameters
return mEngine.createMaterialInstance(this, name); return mEngine.createMaterialInstance(this, name, UboBatchingMode::DEFAULT);
} }
} }
FMaterialInstance* FMaterial::getDefaultInstance() noexcept { FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) { if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
mDefaultMaterialInstance = mEngine.createMaterialInstance(this, mDefaultMaterialInstance =
mDefinition.name.c_str()); mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT);
mDefaultMaterialInstance->setDefaultInstance(true); mDefaultMaterialInstance->setDefaultInstance(true);
} }
return mDefaultMaterialInstance; return mDefaultMaterialInstance;
+1 -1
View File
@@ -108,7 +108,7 @@ public:
backend::CallbackHandler* handler, backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept; utils::Invocable<void(Material*)>&& callback) noexcept;
// Create an instance of this material // Creates an instance of this material, specifying the batching mode.
FMaterialInstance* createInstance(const char* name) const noexcept; FMaterialInstance* createInstance(const char* name) const noexcept;
bool hasParameter(const char* name) const noexcept; bool hasParameter(const char* name) const noexcept;
+48 -16
View File
@@ -58,15 +58,19 @@ using namespace utils;
namespace filament { namespace filament {
using namespace backend; using namespace backend;
using UboBatchingMode = FEngine::UboBatchingMode;
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) {
const char* name) noexcept if (batchingMode != UboBatchingMode::DEFAULT) {
: FMaterialInstance(engine, material, name, return batchingMode == UboBatchingMode::UBO_BATCHING;
engine.features.material.enable_material_instance_uniform_batching) { }
return engine.isUboBatchingEnabled() && domain == MaterialDomain::SURFACE;
} }
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
const char* name, bool useUboBatching) noexcept : mMaterial(material), UboBatchingMode batchingMode) noexcept
: mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()), mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mCulling(CullingMode::BACK), mCulling(CullingMode::BACK),
mShadowCulling(CullingMode::BACK), mShadowCulling(CullingMode::BACK),
@@ -76,10 +80,12 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
mHasScissor(false), mHasScissor(false),
mIsDoubleSided(false), mIsDoubleSided(false),
mIsDefaultInstance(false), mIsDefaultInstance(false),
mUseUboBatching(useUboBatching), mUseUboBatching(
shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())),
mTransparencyMode(TransparencyMode::DEFAULT), mTransparencyMode(TransparencyMode::DEFAULT),
mName(name ? CString(name) : material->getName()) { mName(name ? CString(name) : material->getName()) {
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
<< "UBO batching is not enabled.";
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
// even if the material doesn't have any parameters, we allocate a small UBO because it's // even if the material doesn't have any parameters, we allocate a small UBO because it's
@@ -89,6 +95,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
if (mUseUboBatching) { if (mUseUboBatching) {
mUboData = BufferAllocator::UNALLOCATED; mUboData = BufferAllocator::UNALLOCATED;
engine.getUboManager()->manageMaterialInstance(this);
} else { } else {
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM, mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
BufferUsage::STATIC, ImmutableCString{ material->getName().c_str_safe() }); BufferUsage::STATIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -153,7 +160,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mUseUboBatching(other->mUseUboBatching), mUseUboBatching(other->mUseUboBatching),
mScissorRect(other->mScissorRect), mScissorRect(other->mScissorRect),
mName(name ? CString(name) : other->mName) { mName(name ? CString(name) : other->mName) {
assert_invariant(!mUseUboBatching || engine.isUboBatchingEnabled());
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
FMaterial const* const material = other->getMaterial(); FMaterial const* const material = other->getMaterial();
@@ -161,6 +168,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
if (mUseUboBatching) { if (mUseUboBatching) {
mUboData = BufferAllocator::UNALLOCATED; mUboData = BufferAllocator::UNALLOCATED;
engine.getUboManager()->manageMaterialInstance(this);
} else { } else {
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM, mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
BufferUsage::DYNAMIC, ImmutableCString{ material->getName().c_str_safe() }); BufferUsage::DYNAMIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -193,8 +201,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
} }
} }
FMaterialInstance* FMaterialInstance::duplicate( FMaterialInstance* FMaterialInstance::duplicate(FMaterialInstance const* other,
FMaterialInstance const* other, const char* name) noexcept { const char* name) noexcept {
FMaterial const* const material = other->getMaterial(); FMaterial const* const material = other->getMaterial();
FEngine& engine = material->getEngine(); FEngine& engine = material->getEngine();
return engine.createMaterialInstance(material, other, name); return engine.createMaterialInstance(material, other, name);
@@ -205,6 +213,10 @@ FMaterialInstance::~FMaterialInstance() noexcept = default;
void FMaterialInstance::terminate(FEngine& engine) { void FMaterialInstance::terminate(FEngine& engine) {
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
mDescriptorSet.terminate(driver); mDescriptorSet.terminate(driver);
if (mUseUboBatching) {
engine.getUboManager()->unmanageMaterialInstance(this);
}
auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData); auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
if (ubHandle){ if (ubHandle){
driver.destroyBufferObject(*ubHandle); driver.destroyBufferObject(*ubHandle);
@@ -238,15 +250,21 @@ void FMaterialInstance::commitStreamUniformAssociations(FEngine::DriverApi& driv
void FMaterialInstance::commit(FEngine& engine) const { void FMaterialInstance::commit(FEngine& engine) const {
if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) { if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) {
commit(engine.getDriverApi()); commit(engine.getDriverApi(), engine.getUboManager());
} }
} }
void FMaterialInstance::commit(FEngine::DriverApi& driver) const { void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const {
if (mUniforms.isDirty() || mHasStreamUniformAssociations) { if (mUniforms.isDirty() || mHasStreamUniformAssociations) {
mUniforms.clean(); mUniforms.clean();
if (mUseUboBatching) { if (mUseUboBatching) {
// TODO: update the content by `copyToMemoryMappedBuffer` assert_invariant(uboManager != nullptr);
if (!BufferAllocator::isValid(getAllocationId())) {
// The allocation hasn't happened yet, return.
return;
}
uboManager->updateSlot(driver, getAllocationId(), mUniforms.toBufferDescriptor(driver));
} }
else { else {
auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData); auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
@@ -271,6 +289,10 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver) const {
// TODO: eventually we should remove this in RELEASE builds // TODO: eventually we should remove this in RELEASE builds
fixMissingSamplers(); fixMissingSamplers();
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
// Commit descriptors if needed (e.g. when textures are updated,or the first time) // Commit descriptors if needed (e.g. when textures are updated,or the first time)
mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver); mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver);
} }
@@ -413,6 +435,13 @@ const char* FMaterialInstance::getName() const noexcept {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const { void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
if (!mDescriptorSet.getHandle()) {
return;
}
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) { if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) {
std::call_once(mMissingSamplersFlag, [this] { std::call_once(mMissingSamplersFlag, [this] {
@@ -439,7 +468,8 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
return; return;
} }
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL); mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL,
{ { mUboOffset }, driver });
} }
void FMaterialInstance::assignUboAllocation( void FMaterialInstance::assignUboAllocation(
@@ -449,8 +479,10 @@ void FMaterialInstance::assignUboAllocation(
assert_invariant(mUseUboBatching); assert_invariant(mUseUboBatching);
mUboData = id; mUboData = id;
mUboOffset = offset;
if (BufferAllocator::isValid(id)) { if (BufferAllocator::isValid(id)) {
mDescriptorSet.setBuffer(mMaterial->getDescriptorSetLayout(), 0, ubHandle, offset, // Use dynamic offset during binding, so the offset here is always zero.
mDescriptorSet.setBuffer(mMaterial->getDescriptorSetLayout(), 0, ubHandle, 0,
mUniforms.getSize()); mUniforms.getSize());
} }
} }
+5 -7
View File
@@ -56,11 +56,8 @@ class FTexture;
class FMaterialInstance : public MaterialInstance { class FMaterialInstance : public MaterialInstance {
public: public:
FMaterialInstance(FEngine& engine, FMaterial const* material, FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
const char* name) noexcept; FEngine::UboBatchingMode batchingMode) noexcept;
// Use this constructor when you need to override the ubo batching flag for an individual MI.
FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name, bool useUboBatching) noexcept;
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name); FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
FMaterialInstance(const FMaterialInstance& rhs) = delete; FMaterialInstance(const FMaterialInstance& rhs) = delete;
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete; FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
@@ -75,7 +72,7 @@ public:
void commit(FEngine& engine) const; void commit(FEngine& engine) const;
void commit(FEngine::DriverApi& driver) const; void commit(FEngine::DriverApi& driver, UboManager* uboManager) const;
void use(FEngine::DriverApi& driver, Variant variant = {}) const; void use(FEngine::DriverApi& driver, Variant variant = {}) const;
@@ -287,6 +284,7 @@ private:
}; };
std::variant<BufferAllocator::AllocationId, backend::Handle<backend::HwBufferObject>> mUboData; std::variant<BufferAllocator::AllocationId, backend::Handle<backend::HwBufferObject>> mUboData;
BufferAllocator::allocation_size_t mUboOffset = 0;
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters; tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
mutable DescriptorSet mDescriptorSet; mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms; UniformBuffer mUniforms;
@@ -308,7 +306,7 @@ private:
bool mHasScissor : 1; bool mHasScissor : 1;
bool mIsDoubleSided : 1; bool mIsDoubleSided : 1;
bool mIsDefaultInstance : 1; bool mIsDefaultInstance : 1;
bool mUseUboBatching : 1; const bool mUseUboBatching : 1;
TransparencyMode mTransparencyMode : 2; TransparencyMode mTransparencyMode : 2;
uint64_t mMaterialSortingKey = 0; uint64_t mMaterialSortingKey = 0;
+4
View File
@@ -452,6 +452,8 @@ void FRenderer::endFrame() {
mFrameInfoManager.endFrame(driver); mFrameInfoManager.endFrame(driver);
mFrameSkipper.submitFrame(driver); mFrameSkipper.submitFrame(driver);
engine.submitFrame();
driver.endFrame(mFrameId); driver.endFrame(mFrameId);
// gives the backend a chance to execute periodic tasks // gives the backend a chance to execute periodic tasks
@@ -587,6 +589,8 @@ void FRenderer::renderStandaloneView(FView const* view) {
// happen with Renderer::beginFrame/endFrame. // happen with Renderer::beginFrame/endFrame.
renderInternal(view, true); renderInternal(view, true);
engine.submitFrame();
driver.endFrame(mFrameId); driver.endFrame(mFrameId);
// engine.flush() has already been called by renderInternal(), but we need an extra one // engine.flush() has already been called by renderInternal(), but we need an extra one
+193 -192
View File
@@ -20,9 +20,11 @@
#include "details/BufferAllocator.h" #include "details/BufferAllocator.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <private/utils/Tracing.h>
#include <utils/Logger.h> #include <utils/Logger.h>
#include <vector>
namespace filament { namespace filament {
namespace { namespace {
@@ -33,124 +35,20 @@ using AllocationId = BufferAllocator::AllocationId;
using allocation_size_t = BufferAllocator::allocation_size_t; using allocation_size_t = BufferAllocator::allocation_size_t;
} // anonymous namespace } // anonymous namespace
UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInBytes, // ------------------------------------------------------------------------------------------------
allocation_size_t defaultTotalSizeInBytes) // FenceManager
: mAllocator(defaultTotalSizeInBytes, defaultSlotSizeInBytes) { // ------------------------------------------------------------------------------------------------
reallocate(driver, defaultTotalSizeInBytes);
}
void UboManager::beginFrame(DriverApi& driver,
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& materialInstances) {
// Check finished frames and decrement GPU count accordingly.
checkFenceAndUnlockSlots(driver);
// Actually merge the slots.
mAllocator.releaseFreeSlots();
// Traverse all MIs and see which of them need slot allocation.
AllocationResult allocationResult =
updateMaterialInstanceAllocations(materialInstances, ON_DEMAND);
if (allocationResult == SUCCESS) {
// No need to grow the buffer, so we can just map the buffer for writing and return.
mMemoryMappedBufferHandle = driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT,
"UboManager");
void UboManager::FenceManager::track(DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds) {
if (allocationIds.empty()) {
return; return;
} }
mFenceAllocationList.emplace_back(driver.createFence(), std::move(allocationIds));
// Calculate the required size and grow the Ubo.
const allocation_size_t requiredSize = calculateRequiredSize(materialInstances);
reallocate(driver, requiredSize);
// Allocate slots for each MI on the new Ubo.
UTILS_UNUSED_IN_RELEASE const AllocationResult needReallocationAgain =
updateMaterialInstanceAllocations(materialInstances, ALWAYS);
assert_invariant(needReallocationAgain != REALLOCATION_REQUIRED);
// Map the buffer so that we can write to it
mMemoryMappedBufferHandle =
driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT, "UboManager");
// Migrate all MI data to the new allocated slots.
for (const auto& materialInstance : materialInstances) {
materialInstance.second.forEach([this, &driver](const FMaterialInstance* mi) {
if (!mi->isUsingUboBatching()) {
return;
}
const AllocationId allocationId = mi->getAllocationId();
assert_invariant(BufferAllocator::isValid(allocationId));
updateSlot(driver, allocationId, mi->getUniformBuffer().toBufferDescriptor(driver));
});
}
} }
void UboManager::finishBeginFrame(DriverApi& driver) { void UboManager::FenceManager::reclaimCompletedResources(DriverApi& driver,
if (mMemoryMappedBufferHandle) { std::function<void(AllocationId)> const& onReclaimed) {
driver.unmapBuffer(mMemoryMappedBufferHandle); FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
mMemoryMappedBufferHandle.clear();
}
}
void UboManager::endFrame(DriverApi& driver,
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& materialInstances) {
BufferAllocator& allocator = mAllocator;
std::unordered_set<AllocationId> allocationIds;
for (const auto& materialInstance : materialInstances) {
materialInstance.second.forEach([&allocator, &allocationIds](const FMaterialInstance* mi) {
if (!mi->isUsingUboBatching()) {
return;
}
const AllocationId id = mi->getAllocationId();
if (!BufferAllocator::isValid(id)) {
return;
}
allocator.acquireGpu(id);
allocationIds.insert(id);
});
}
mFenceAllocationList.emplace_back( driver.createFence(), std::move(allocationIds) );
}
void UboManager::terminate(DriverApi& driver) {
for (auto& [fence, _]: mFenceAllocationList) {
if (fence) {
driver.destroyFence(std::move(fence));
}
}
mFenceAllocationList.clear();
driver.destroyBufferObject(mUbHandle);
}
void UboManager::updateSlot(DriverApi& driver, AllocationId id,
BufferDescriptor bufferDescriptor) const {
if (!mMemoryMappedBufferHandle)
return;
const allocation_size_t offset = mAllocator.getAllocationOffset(id);
driver.copyToMemoryMappedBuffer(mMemoryMappedBufferHandle, offset, std::move(bufferDescriptor));
}
void UboManager::retireSlot(BufferAllocator::AllocationId id) {
if (!BufferAllocator::isValid(id))
return;
mAllocator.retire(id);
}
allocation_size_t UboManager::getTotalSize() const noexcept {
return mUboSize;
}
allocation_size_t UboManager::getAllocationOffset(AllocationId id) const {
return mAllocator.getAllocationOffset(id);
}
void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
uint32_t signaledCount = 0; uint32_t signaledCount = 0;
bool seenSignaledFence = false; bool seenSignaledFence = false;
@@ -166,10 +64,8 @@ void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
signaledCount++; signaledCount++;
#ifndef NDEBUG #ifndef NDEBUG
if (UTILS_UNLIKELY(status != FenceStatus::CONDITION_SATISFIED)) { if (UTILS_UNLIKELY(status != FenceStatus::CONDITION_SATISFIED)) {
LOG(WARNING) << "A fence is either an error or hasn't signaled, but the new fence has " LOG(WARNING) << "A fence is either in an error state or hasn't signaled, but a newer "
"been " "fence has. Will release the resource anyway.";
"signaled. Will release the resource anyways."
<< io::endl;
} }
#endif #endif
continue; continue;
@@ -188,10 +84,10 @@ void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
auto firstToKeep = mFenceAllocationList.begin() + signaledCount; auto firstToKeep = mFenceAllocationList.begin() + signaledCount;
// Release GPU locks for all completed fences. // Invoke the callback for all resources protected by completed fences.
for (auto it = mFenceAllocationList.begin(); it != firstToKeep; ++it) { for (auto it = mFenceAllocationList.begin(); it != firstToKeep; ++it) {
for (const AllocationId& id: it->second) { for (const AllocationId& id : it->second) {
mAllocator.releaseGpu(id); onReclaimed(id);
} }
// Destroy the fence handle as it's no longer needed. // Destroy the fence handle as it's no longer needed.
driver.destroyFence(std::move(it->first)); driver.destroyFence(std::move(it->first));
@@ -200,106 +96,211 @@ void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
mFenceAllocationList.erase(mFenceAllocationList.begin(), firstToKeep); mFenceAllocationList.erase(mFenceAllocationList.begin(), firstToKeep);
} }
UboManager::AllocationResult UboManager::updateMaterialInstanceAllocations( void UboManager::FenceManager::reset(DriverApi& driver) {
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& for (auto& [fence, _] : mFenceAllocationList) {
materialInstances, if (fence) {
AllocationMode allocationMode) { driver.destroyFence(std::move(fence));
AllocationResult result = SUCCESS; }
for (const auto& materialInstance: materialInstances) {
materialInstance.second.forEach([this, &result, allocationMode](FMaterialInstance* mi) {
if (tryAllocateMaterialInstanceSlot(mi, mAllocator, mUbHandle, allocationMode) ==
REALLOCATION_REQUIRED) {
result = REALLOCATION_REQUIRED;
}
});
} }
mFenceAllocationList.clear();
return result;
} }
UboManager::AllocationResult UboManager::tryAllocateMaterialInstanceSlot(FMaterialInstance* mi,
BufferAllocator& allocator, const Handle<HwBufferObject>& ubHandle, // ------------------------------------------------------------------------------------------------
AllocationMode allocationMode) { // UboManager
if (!mi->isUsingUboBatching()) { // ------------------------------------------------------------------------------------------------
return SUCCESS;
UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInBytes,
allocation_size_t defaultTotalSizeInBytes)
: mAllocator(defaultTotalSizeInBytes, defaultSlotSizeInBytes) {
reallocate(driver, defaultTotalSizeInBytes);
}
void UboManager::beginFrame(DriverApi& driver) {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
// Check finished frames and decrement GPU count accordingly.
mFenceManager.reclaimCompletedResources(driver,
[this](AllocationId id) { mAllocator.releaseGpu(id); });
// Actually merge the slots.
mAllocator.releaseFreeSlots();
// Traverse all MIs and see which of them need slot allocation.
if (allocateOnDemand() == SUCCESS) {
// No need to grow the buffer, so we can just map the buffer for writing and return.
mMemoryMappedBufferHandle = driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT,
"UboManager");
return;
} }
const AllocationId id = mi->getAllocationId(); // Calculate the required size and grow the Ubo.
auto allocateAndAssign = [&](AllocationId originalId) -> AllocationResult { const allocation_size_t requiredSize = calculateRequiredSize();
auto [newId, newOffset] = allocator.allocate(mi->getUniformBuffer().getSize()); reallocate(driver, requiredSize);
// Special handling for instances that were previously UNALLOCATED:
// If the new allocation also fails, keep it UNALLOCATED to signal initial failure. // Allocate slots for each MI on the new Ubo.
if (originalId == BufferAllocator::UNALLOCATED && allocateAllInstances();
newId == BufferAllocator::REALLOCATION_REQUIRED) {
newId = BufferAllocator::UNALLOCATED; // Map the buffer so that we can write to it
mMemoryMappedBufferHandle =
driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT, "UboManager");
// Invalidate the migrated MIs, so that next commit() call must be triggered.
for (const auto* mi : mManagedInstances) {
mi->getUniformBuffer().invalidate();
}
}
void UboManager::finishBeginFrame(DriverApi& driver) {
if (mMemoryMappedBufferHandle) {
driver.unmapBuffer(mMemoryMappedBufferHandle);
mMemoryMappedBufferHandle.clear();
}
}
void UboManager::endFrame(DriverApi& driver) {
std::unordered_set<AllocationId> allocationIds;
for (const auto* mi : mManagedInstances) {
const AllocationId id = mi->getAllocationId();
if (UTILS_UNLIKELY(!BufferAllocator::isValid(id))) {
continue;
} }
// Even if the allocation is not valid, we need to set it to let the following mAllocator.acquireGpu(id);
// process knows. allocationIds.insert(id);
mi->assignUboAllocation(ubHandle, newId, newOffset); }
mFenceManager.track(driver, std::move(allocationIds));
}
void UboManager::terminate(DriverApi& driver) {
mFenceManager.reset(driver);
driver.destroyBufferObject(mUbHandle);
}
void UboManager::updateSlot(DriverApi& driver, AllocationId id,
BufferDescriptor bufferDescriptor) const {
if (!mMemoryMappedBufferHandle) {
return;
}
const allocation_size_t offset = mAllocator.getAllocationOffset(id);
driver.copyToMemoryMappedBuffer(mMemoryMappedBufferHandle, offset, std::move(bufferDescriptor));
}
void UboManager::manageMaterialInstance(FMaterialInstance* instance) {
mPendingInstances.insert(instance);
}
void UboManager::unmanageMaterialInstance(FMaterialInstance* materialInstance) {
AllocationId id = materialInstance->getAllocationId();
mPendingInstances.erase(materialInstance);
mManagedInstances.erase(materialInstance);
if (!BufferAllocator::isValid(id)) {
return;
}
mAllocator.retire(id);
materialInstance->assignUboAllocation(mUbHandle, BufferAllocator::UNALLOCATED, 0);
}
UboManager::AllocationResult UboManager::allocateOnDemand() {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
bool reallocationNeeded = false;
// Pass 1: Allocate slots for new material instances (that don't have a slot yet).
for (auto* mi : mPendingInstances) {
mManagedInstances.insert(mi);
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
// Even if the newId is not valid, we assign it to the MI so that the following process knows
// this material instance was not allocated successfully. Then we can calculate the new
// required UBO size properly.
mi->assignUboAllocation(mUbHandle, newId, newOffset);
if (!BufferAllocator::isValid(newId)) { if (!BufferAllocator::isValid(newId)) {
return REALLOCATION_REQUIRED; reallocationNeeded = true;
} }
return SUCCESS; }
}; mPendingInstances.clear();
if (!BufferAllocator::isValid(id) || allocationMode == ALWAYS) { // Pass 2: Allocate slots for existing material instances that need to be orphaned.
// The material instance is first time being allocated (if !isValid) for (auto* mi: mManagedInstances) {
// or is being forcibly re-allocated (if ALWAYS). if (!BufferAllocator::isValid(mi->getAllocationId())) {
return allocateAndAssign(id); continue;
}
// This instance doesn't need orphaning.
if (!mi->getUniformBuffer().isDirty() || !mAllocator.isLockedByGpu(mi->getAllocationId())) {
continue;
}
mAllocator.retire(mi->getAllocationId());
// If the space is already not sufficient, we don't need to give another try on allocation.
if (reallocationNeeded) {
mi->assignUboAllocation(mUbHandle, REALLOCATION_REQUIRED, 0);
continue;
}
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
// Even if the newId is not valid, we assign it to the MI so that the following process knows
// this material instance was not allocated successfully. Then we can calculate the new
// required UBO size properly.
mi->assignUboAllocation(mUbHandle, newId, newOffset);
if (!BufferAllocator::isValid(newId)) {
reallocationNeeded = true;
}
} }
if (mi->getUniformBuffer().isDirty() && allocator.isLockedByGpu(id)) { return reallocationNeeded ? REALLOCATION_REQUIRED : SUCCESS;
// If the uniform buffer is updated and the slot is still being locked by GPU, }
// we will need to allocate a new slot to write the updated content.
// This is known as "orphaning".
allocator.retire(id);
return allocateAndAssign(id);
}
// Else, the slot is valid and no need to allocate a new slot.
return SUCCESS; void UboManager::allocateAllInstances() {
for (auto* mi: mManagedInstances) {
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
assert_invariant(BufferAllocator::isValid(newId));
mi->assignUboAllocation(mUbHandle, newId, newOffset);
}
}
allocation_size_t UboManager::getTotalSize() const noexcept {
return mUboSize;
}
allocation_size_t UboManager::getAllocationOffset(AllocationId id) const {
return mAllocator.getAllocationOffset(id);
} }
void UboManager::reallocate(DriverApi& driver, allocation_size_t requiredSize) { void UboManager::reallocate(DriverApi& driver, allocation_size_t requiredSize) {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
if (mUbHandle) { if (mUbHandle) {
driver.destroyBufferObject(mUbHandle); driver.destroyBufferObject(mUbHandle);
} }
for (auto& [fence, allocations]: mFenceAllocationList) { mFenceManager.reset(driver);
driver.destroyFence(std::move(fence));
}
mFenceAllocationList.clear();
mAllocator.reset(requiredSize); mAllocator.reset(requiredSize);
mUboSize = requiredSize; mUboSize = requiredSize;
mUbHandle = driver.createBufferObject(requiredSize, BufferObjectBinding::UNIFORM, mUbHandle = driver.createBufferObject(requiredSize, BufferObjectBinding::UNIFORM,
BufferUsage::DYNAMIC | BufferUsage::SHARED_WRITE_BIT); BufferUsage::DYNAMIC | BufferUsage::SHARED_WRITE_BIT);
} }
allocation_size_t UboManager::calculateRequiredSize( allocation_size_t UboManager::calculateRequiredSize() {
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances) {
BufferAllocator& allocator = mAllocator;
allocation_size_t newBufferSize = 0; allocation_size_t newBufferSize = 0;
for (const auto& materialInstance: materialInstances) { for (const auto* mi: mManagedInstances) {
materialInstance.second.forEach([&newBufferSize, &allocator](const FMaterialInstance* mi) { const AllocationId allocationId = mi->getAllocationId();
if (!mi->isUsingUboBatching()) { if (allocationId == BufferAllocator::REALLOCATION_REQUIRED) {
return; // For MIs whose parameters have been updated, aside from the slot it is being
} // occupied by the GPU, we need to preserve an additional slot for it.
newBufferSize += 2 * mAllocator.alignUp(mi->getUniformBuffer().getSize());
const AllocationId allocationId = mi->getAllocationId(); } else {
if (allocationId == BufferAllocator::REALLOCATION_REQUIRED) { newBufferSize += mAllocator.alignUp(mi->getUniformBuffer().getSize());
// For MIs whose parameters have been updated, aside from the slot it is being }
// occupied by the GPU, we need to preserve an additional slot for it.
newBufferSize += 2 * allocator.alignUp(mi->getUniformBuffer().getSize());
} else {
newBufferSize += allocator.alignUp(mi->getUniformBuffer().getSize());
}
});
} }
return allocator.alignUp(newBufferSize * BUFFER_SIZE_GROWTH_MULTIPLIER); return mAllocator.alignUp(newBufferSize * BUFFER_SIZE_GROWTH_MULTIPLIER);
} }
} // namespace filament } // namespace filament
+72 -34
View File
@@ -17,7 +17,6 @@
#ifndef TNT_FILAMENT_DETAILS_UBOMANAGER_H #ifndef TNT_FILAMENT_DETAILS_UBOMANAGER_H
#define TNT_FILAMENT_DETAILS_UBOMANAGER_H #define TNT_FILAMENT_DETAILS_UBOMANAGER_H
#include "ResourceList.h"
#include "backend/DriverApiForward.h" #include "backend/DriverApiForward.h"
#include "details/BufferAllocator.h" #include "details/BufferAllocator.h"
@@ -25,7 +24,11 @@
#include <backend/Handle.h> #include <backend/Handle.h>
#include <private/backend/DriverApi.h> #include <private/backend/DriverApi.h>
#include <functional>
#include <unordered_set> #include <unordered_set>
#include <vector>
class UboManagerTest;
namespace filament { namespace filament {
@@ -40,6 +43,47 @@ class FMaterialInstance;
// undefined behavior. // undefined behavior.
class UboManager { class UboManager {
public: public:
// This utility tracks resources that are in-use by the GPU across multiple frames.
// It uses backend fences to determine when the GPU has finished with a set of resources,
// allowing them to be safely reclaimed or reused.
//
// The typical usage is to `track()` a set of resources at the end of a frame and
// call `reclaimCompletedResources()` at the beginning of a future frame to free up
// resources from completed GPU work.
//
// This class is designed for single-threaded access.
class FenceManager {
public:
using AllocationId = BufferAllocator::AllocationId;
FenceManager() = default;
~FenceManager() = default;
FenceManager(FenceManager const&) = delete;
FenceManager(FenceManager&&) = delete;
// Creates a new fence to track a set of allocation IDs for the current frame.
// This marks the beginning of GPU's usage of these resources.
void track(backend::DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds);
// Checks all tracked fences and invokes a callback for resources associated with
// completed fences. This should be called once per frame.
void reclaimCompletedResources(backend::DriverApi& driver,
std::function<void(AllocationId)> const& onReclaimed);
// Destroys all tracked fences and clears the tracking list.
// This is used for cleanup during termination or major reallocations.
void reset(backend::DriverApi& driver);
private:
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame.
using FenceAndAllocations =
std::pair<backend::Handle<backend::HwFence>, std::unordered_set<AllocationId>>;
std::vector<FenceAndAllocations> mFenceAllocationList;
};
explicit UboManager(backend::DriverApi& driver, explicit UboManager(backend::DriverApi& driver,
BufferAllocator::allocation_size_t defaultSlotSizeInBytes, BufferAllocator::allocation_size_t defaultSlotSizeInBytes,
BufferAllocator::allocation_size_t defaultTotalSizeInBytes); BufferAllocator::allocation_size_t defaultTotalSizeInBytes);
@@ -53,9 +97,8 @@ public:
// instances with modified uniforms). // instances with modified uniforms).
// 3. Reallocating a larger shared UBO if the current one is insufficient. // 3. Reallocating a larger shared UBO if the current one is insufficient.
// 4. Mapping the shared UBO into CPU-accessible memory to prepare for uniform data writes. // 4. Mapping the shared UBO into CPU-accessible memory to prepare for uniform data writes.
void beginFrame(backend::DriverApi& driver, // Note that it must happen before committing all MIs.
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& void beginFrame(backend::DriverApi& driver);
materialInstances);
// Unmap the buffer here // Unmap the buffer here
void finishBeginFrame(backend::DriverApi& driver); void finishBeginFrame(backend::DriverApi& driver);
@@ -63,62 +106,57 @@ public:
// Create a fence and associate it with a set of allocation ids. // Create a fence and associate it with a set of allocation ids.
// The gpuUseCount of these allocations will be incremented, and they will be decremented // The gpuUseCount of these allocations will be incremented, and they will be decremented
// After the corresponding frame has been done. // After the corresponding frame has been done.
void endFrame(backend::DriverApi& driver, void endFrame(backend::DriverApi& driver);
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances);
void terminate(backend::DriverApi& driver); void terminate(backend::DriverApi& driver);
void updateSlot(backend::DriverApi& driver, BufferAllocator::AllocationId id, void updateSlot(backend::DriverApi& driver, BufferAllocator::AllocationId id,
backend::BufferDescriptor bufferDescriptor) const; backend::BufferDescriptor bufferDescriptor) const;
// Call this when a material instance is no longer holding a slot. e.g. it is destroyed. // Call this to register a new material instance to UboManager.
void retireSlot(BufferAllocator::AllocationId id); void manageMaterialInstance(FMaterialInstance* instance);
// Call this when a material instance is destroyed.
void unmanageMaterialInstance(FMaterialInstance* materialInstance);
// Returns the size of the actual UBO. Note that when there's allocation failed, it will be // Returns the size of the actual UBO. Note that when there's allocation failed, it will be
// reallocated to a bigger size at the next frame. // reallocated to a bigger size at the next frame.
[[nodiscard]] BufferAllocator::allocation_size_t getTotalSize() const noexcept; [[nodiscard]] BufferAllocator::allocation_size_t getTotalSize() const noexcept;
// For testing
[[nodiscard]] backend::MemoryMappedBufferHandle getMemoryMappedBufferHandle() const noexcept {
return mMemoryMappedBufferHandle;
}
private: private:
friend class ::UboManagerTest;
constexpr static float BUFFER_SIZE_GROWTH_MULTIPLIER = 1.5f; constexpr static float BUFFER_SIZE_GROWTH_MULTIPLIER = 1.5f;
// Query the offset by the allocation id.
[[nodiscard]] BufferAllocator::allocation_size_t getAllocationOffset(
BufferAllocator::AllocationId id) const;
void checkFenceAndUnlockSlots(backend::DriverApi& driver);
enum AllocationResult { enum AllocationResult {
SUCCESS, SUCCESS,
REALLOCATION_REQUIRED REALLOCATION_REQUIRED
}; };
enum AllocationMode { // Query the offset by the allocation id.
ON_DEMAND, [[nodiscard]] BufferAllocator::allocation_size_t getAllocationOffset(
ALWAYS BufferAllocator::AllocationId id) const;
};
AllocationResult allocateOnDemand();
void allocateAllInstances();
AllocationResult updateMaterialInstanceAllocations(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances,
AllocationMode allocationMode);
static AllocationResult tryAllocateMaterialInstanceSlot(FMaterialInstance* mi,
BufferAllocator& allocator, const backend::Handle<backend::HwBufferObject>& ubHandle,
AllocationMode allocationMode);
void reallocate(backend::DriverApi& driver, BufferAllocator::allocation_size_t requiredSize); void reallocate(backend::DriverApi& driver, BufferAllocator::allocation_size_t requiredSize);
BufferAllocator::allocation_size_t calculateRequiredSize(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& BufferAllocator::allocation_size_t calculateRequiredSize();
materialInstances);
backend::Handle<backend::HwBufferObject> mUbHandle; backend::Handle<backend::HwBufferObject> mUbHandle;
backend::MemoryMappedBufferHandle mMemoryMappedBufferHandle; backend::MemoryMappedBufferHandle mMemoryMappedBufferHandle;
BufferAllocator::allocation_size_t mUboSize{}; BufferAllocator::allocation_size_t mUboSize{};
std::unordered_set<FMaterialInstance*> mPendingInstances;
std::unordered_set<FMaterialInstance*> mManagedInstances;
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame. FenceManager mFenceManager;
using FenceAllocationList = std::vector<std::pair<backend::Handle<backend::HwFence>,
std::unordered_set<BufferAllocator::AllocationId>>>;
FenceAllocationList mFenceAllocationList;
BufferAllocator mAllocator; BufferAllocator mAllocator;
}; };
+33 -41
View File
@@ -285,53 +285,45 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const Builder& builder)
// calculate buffer sizes // calculate buffer sizes
size_t bufferSizes[MAX_VERTEX_BUFFER_COUNT] = {}; size_t bufferSizes[MAX_VERTEX_BUFFER_COUNT] = {};
#pragma nounroll
for (size_t i = 0, n = mAttributes.size(); i < n; ++i) { auto shouldCreateBuffer = [this](size_t attributeIndex) {
if (mDeclaredAttributes[i]) { const uint8_t slot = mAttributes[attributeIndex].buffer;
const uint32_t offset = mAttributes[i].offset; return mDeclaredAttributes[attributeIndex] && slot != Attribute::BUFFER_UNUSED &&
const uint8_t stride = mAttributes[i].stride; !mBufferObjects[slot];
const uint8_t slot = mAttributes[i].buffer; };
const size_t end = offset + mVertexCount * stride; auto updateBufferSize = [&bufferSizes, this](size_t attributeIndex) {
if (slot != Attribute::BUFFER_UNUSED) { const uint32_t offset = mAttributes[attributeIndex].offset;
assert_invariant(slot < MAX_VERTEX_BUFFER_COUNT); const uint8_t stride = mAttributes[attributeIndex].stride;
bufferSizes[slot] = std::max(bufferSizes[slot], end); const uint8_t slot = mAttributes[attributeIndex].buffer;
} const size_t end = offset + mVertexCount * stride;
} assert_invariant(slot < MAX_VERTEX_BUFFER_COUNT);
} bufferSizes[slot] = std::max(bufferSizes[slot], end);
};
if (!mBufferObjectsEnabled) { if (!mBufferObjectsEnabled) {
// If buffer objects are not enabled at the API level, then we create them internally.
#pragma nounroll #pragma nounroll
for (size_t index = 0; index < MAX_VERTEX_BUFFER_COUNT; ++index) { for (size_t i = 0, n = mAttributes.size(); i < n; ++i) {
size_t const i = mAttributes[index].buffer; if (shouldCreateBuffer(i)) {
if (i != Attribute::BUFFER_UNUSED) { updateBufferSize(i);
assert_invariant(bufferSizes[i] > 0);
if (!mBufferObjects[i]) {
BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i],
BufferObjectBinding::VERTEX, BufferUsage::STATIC,
utils::ImmutableCString{ builder.getName() });
driver.setVertexBufferObject(mHandle, i, bo);
mBufferObjects[i] = bo;
}
} }
} }
} else { } else if (mAdvancedSkinningEnabled) {
// in advanced skinning mode, we manage the BONE_INDICES and BONE_WEIGHTS arrays ourselves, // For advanced skinning mode, only relevant buffers (BONE_INDICES & BONE_WEIGHTS) are
// so we have to set the corresponding buffer objects. // created. We manually populated the relevant attributes for those buffers above.
if (mAdvancedSkinningEnabled) { updateBufferSize(BONE_INDICES);
for (auto const index : { BONE_INDICES, BONE_WEIGHTS }) { updateBufferSize(BONE_WEIGHTS);
size_t const i = mAttributes[index].buffer; }
assert_invariant(i != Attribute::BUFFER_UNUSED);
assert_invariant(bufferSizes[i] > 0); // create buffers
if (!mBufferObjects[i]) { for (size_t i = 0; i < MAX_VERTEX_BUFFER_COUNT; ++i) {
BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], if (bufferSizes[i] == 0 || mBufferObjects[i]) {
BufferObjectBinding::VERTEX, BufferUsage::STATIC, continue;
utils::ImmutableCString{ builder.getName() });
driver.setVertexBufferObject(mHandle, i, bo);
mBufferObjects[i] = bo;
}
}
} }
BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i],
BufferObjectBinding::VERTEX, BufferUsage::STATIC,
utils::ImmutableCString{ builder.getName() });
driver.setVertexBufferObject(mHandle, i, bo);
mBufferObjects[i] = bo;
} }
} }
+4
View File
@@ -338,6 +338,10 @@ public:
return mDynamicResolution; return mDynamicResolution;
} }
math::float2 getLastDynamicResolutionScale() const noexcept {
return mScale;
}
void setRenderQuality(RenderQuality const& renderQuality) noexcept { void setRenderQuality(RenderQuality const& renderQuality) noexcept {
mRenderQuality = renderQuality; mRenderQuality = renderQuality;
} }
+1 -1
View File
@@ -39,7 +39,7 @@ void PostProcessDescriptorSet::init(FEngine& engine) noexcept {
// create the descriptor-set layout // create the descriptor-set layout
mDescriptorSetLayout = filament::DescriptorSetLayout{ mDescriptorSetLayout = filament::DescriptorSetLayout{
engine.getDescriptorSetLayoutFactory(), engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), descriptor_sets::getPostProcessLayout() }; engine.getDriverApi(), descriptor_sets::getDepthVariantLayout() };
// create the descriptor-set from the layout // create the descriptor-set from the layout
mDescriptorSet = DescriptorSet{ "PostProcessDescriptorSet", mDescriptorSetLayout }; mDescriptorSet = DescriptorSet{ "PostProcessDescriptorSet", mDescriptorSetLayout };
+6 -1
View File
@@ -44,18 +44,23 @@ list(APPEND RESGEN_SOURCE ${DUMMY_SRC})
# away in Release builds # away in Release builds
if (TNT_DEV) if (TNT_DEV)
add_executable(test_${TARGET} add_executable(test_${TARGET}
MockDriver.h
filament_AtlasAllocator_test.cpp filament_AtlasAllocator_test.cpp
test_BufferAllocator.cpp test_BufferAllocator.cpp
test_BufferAllocatorStress.cpp test_BufferAllocatorStress.cpp
test_CircularQueue.cpp test_CircularQueue.cpp
test_FenceManager.cpp
test_UboManager.cpp
filament_test_exposure.cpp filament_test_exposure.cpp
filament_rendering_test.cpp filament_rendering_test.cpp
filament_bimap_test.cpp filament_bimap_test.cpp
filament_framegraph_test.cpp filament_framegraph_test.cpp
filament_test.cpp) filament_test.cpp
${RESGEN_SOURCE})
target_link_libraries(test_${TARGET} PRIVATE filament gtest) target_link_libraries(test_${TARGET} PRIVATE filament gtest)
target_compile_options(test_${TARGET} PRIVATE ${COMPILER_FLAGS}) target_compile_options(test_${TARGET} PRIVATE ${COMPILER_FLAGS})
target_include_directories(test_${TARGET} PRIVATE ${RESOURCE_DIR})
set_target_properties(test_${TARGET} PROPERTIES FOLDER Tests) set_target_properties(test_${TARGET} PROPERTIES FOLDER Tests)
add_executable(test_depth depth_test.cpp) add_executable(test_depth depth_test.cpp)
+149
View File
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_TEST_MOCK_DRIVER_H
#define TNT_FILAMENT_TEST_MOCK_DRIVER_H
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "private/backend/CommandStream.h"
#include "private/backend/Driver.h"
using namespace filament;
using namespace filament::backend;
using ::testing::_;
using ::testing::InSequence;
using ::testing::NiceMock;
using ::testing::Return;
// A mock implementation of the backend::Driver for unit testing.
//
// This mock is primarily designed for testing synchronous APIs and functions that
// return a value immediately (those with the "S" suffix, like `createBufferObjectS`).
// It does not fully support asynchronous command stream behavior, so its use for
// testing complex rendering logic is limited.
class MockDriver : public Driver {
public:
MockDriver() = default;
~MockDriver() override = default;
uint64_t nextFakeHandle = 1;
MOCK_METHOD(void, terminate, (), (override));
MOCK_METHOD(backend::StreamHandle, createStreamNative,
(void* stream, utils::ImmutableCString tag), (override));
MOCK_METHOD(backend::StreamHandle, createStreamAcquired, (utils::ImmutableCString tag),
(override));
MOCK_METHOD(void, setAcquiredImage,
(backend::StreamHandle stream, void* image, const math::mat3f& transform,
backend::CallbackHandler* handler, backend::StreamCallback cb, void* userData),
(override));
MOCK_METHOD(void, setStreamDimensions,
(backend::StreamHandle stream, uint32_t width, uint32_t height), (override));
MOCK_METHOD(int64_t, getStreamTimestamp, (backend::StreamHandle stream), (override));
MOCK_METHOD(void, updateStreams, (backend::DriverApi * driver), (override));
MOCK_METHOD(backend::FenceStatus, getFenceStatus, (backend::FenceHandle fh), (override));
MOCK_METHOD(backend::FenceStatus, fenceWait, (backend::FenceHandle fh, uint64_t timeout),
(override));
MOCK_METHOD(void, getPlatformSync,
(backend::SyncHandle sh, backend::CallbackHandler* handler,
backend::Platform::SyncCallback cb, void* userData),
(override));
MOCK_METHOD(bool, isTextureFormatSupported, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isTextureSwizzleSupported, (), (override));
MOCK_METHOD(bool, isTextureFormatMipmappable, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isRenderTargetFormatSupported, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isFrameBufferFetchSupported, (), (override));
MOCK_METHOD(bool, isFrameBufferFetchMultiSampleSupported, (), (override));
MOCK_METHOD(bool, isFrameTimeSupported, (), (override));
MOCK_METHOD(bool, isAutoDepthResolveSupported, (), (override));
MOCK_METHOD(bool, isSRGBSwapChainSupported, (), (override));
MOCK_METHOD(bool, isMSAASwapChainSupported, (uint32_t samples), (override));
MOCK_METHOD(bool, isProtectedContentSupported, (), (override));
MOCK_METHOD(bool, isStereoSupported, (), (override));
MOCK_METHOD(bool, isParallelShaderCompileSupported, (), (override));
MOCK_METHOD(bool, isDepthStencilResolveSupported, (), (override));
MOCK_METHOD(bool, isDepthStencilBlitSupported, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isProtectedTexturesSupported, (), (override));
MOCK_METHOD(bool, isDepthClampSupported, (), (override));
MOCK_METHOD(uint8_t, getMaxDrawBuffers, (), (override));
MOCK_METHOD(size_t, getMaxUniformBufferSize, (), (override));
MOCK_METHOD(size_t, getMaxTextureSize, (backend::SamplerType target), (override));
MOCK_METHOD(size_t, getMaxArrayTextureLayers, (), (override));
MOCK_METHOD(math::float2, getClipSpaceParams, (), (override));
MOCK_METHOD(void, setupExternalImage2, (backend::Platform::ExternalImageHandleRef image),
(override));
MOCK_METHOD(void, setupExternalImage, (void* image), (override));
MOCK_METHOD(backend::TimerQueryResult, getTimerQueryValue,
(backend::TimerQueryHandle tqh, uint64_t* elapsedTime), (override));
MOCK_METHOD(bool, isWorkaroundNeeded, (backend::Workaround workaround), (override));
MOCK_METHOD(backend::FeatureLevel, getFeatureLevel, (), (override));
MOCK_METHOD(size_t, getUniformBufferOffsetAlignment, (), (override));
MOCK_METHOD(bool, isCompositorTimingSupported, (), (override));
MOCK_METHOD(bool, queryFrameTimestamps, (SwapChainHandle, uint64_t, FrameTimestamps*), (override));
MOCK_METHOD(bool, queryCompositorTiming, (SwapChainHandle, CompositorTiming*), (override));
MOCK_METHOD(void, fenceCancel, (backend::FenceHandle), (override));
ShaderModel getShaderModel() const noexcept final { return ShaderModel::DESKTOP; }
utils::FixedCapacityVector<ShaderLanguage> getShaderLanguages(
ShaderLanguage preferredLanguage) const noexcept final {
return {
ShaderLanguage::ESSL3,
ShaderLanguage::ESSL1,
ShaderLanguage::SPIRV,
ShaderLanguage::MSL,
ShaderLanguage::METAL_LIBRARY,
ShaderLanguage::WGSL,
};
}
void debugCommandBegin(CommandStream* cmds, bool synchronous,
const char* methodName) noexcept override {}
void debugCommandEnd(CommandStream* cmds, bool synchronous,
const char* methodName) noexcept override {}
void purge() noexcept override {}
template<typename T>
friend class ConcreteDispatcher;
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
UTILS_ALWAYS_INLINE inline void methodName(paramsDecl) {}
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params)
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
RetType methodName##S() noexcept override { \
return RetType((RetType::HandleId) nextFakeHandle++); \
} \
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl) {}
#include "private/backend/DriverAPI.inc"
Dispatcher getDispatcher() const noexcept override {
Dispatcher dispatcher;
// Currently we don't have a good way to mock the functions for asynchronous backend API.
// We set all API to nullptr to let the users know it doesn't work.
#define DECL_DRIVER_API(methodName, paramsDecl, params) dispatcher.methodName##_ = nullptr;
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
dispatcher.methodName##_ = nullptr;
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params)
#include "private/backend/DriverAPI.inc"
return dispatcher;
}
};
#endif // TNT_FILAMENT_TEST_MOCK_DRIVER_H
+158
View File
@@ -0,0 +1,158 @@
/*
* Copyright (C) 2025 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 "MockDriver.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "details/UboManager.h"
#include "private/backend/CommandBufferQueue.h"
#include "private/backend/CommandStream.h"
#include "private/backend/Driver.h"
namespace {
using namespace filament;
using namespace backend;
using ::testing::_;
using ::testing::Return;
static constexpr size_t CONFIG_MIN_COMMAND_BUFFERS_SIZE = 1 * 1024 * 1024;
static constexpr size_t CONFIG_COMMAND_BUFFERS_SIZE = 3 * CONFIG_MIN_COMMAND_BUFFERS_SIZE;
class FenceManagerTest : public ::testing::Test {
protected:
FenceManagerTest()
: mCommandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, CONFIG_COMMAND_BUFFERS_SIZE,
/*mPaused=*/false),
mCommandStream(mMockDriver, mCommandBufferQueue.getCircularBuffer()),
mDriverApi(mCommandStream) {}
MockDriver mMockDriver;
CommandBufferQueue mCommandBufferQueue;
CommandStream mCommandStream;
DriverApi& mDriverApi;
UboManager::FenceManager mFenceManager;
};
TEST_F(FenceManagerTest, TrackEmptySet) {
mFenceManager.track(mDriverApi, {});
EXPECT_EQ(mMockDriver.nextFakeHandle, 1); // No fence allocation
}
TEST_F(FenceManagerTest, TrackNonEmptySet) {
mFenceManager.track(mDriverApi, { 1, 2, 3 });
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
}
TEST_F(FenceManagerTest, ReclaimWithNoFences) {
std::vector<UboManager::FenceManager::AllocationId> reclaimedIds;
mFenceManager.reclaimCompletedResources(mDriverApi,
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.push_back(id); });
EXPECT_TRUE(reclaimedIds.empty());
}
TEST_F(FenceManagerTest, ReclaimWhenFenceNotSignaled) {
mFenceManager.track(mDriverApi, { 10 });
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(1)))
.WillOnce(Return(FenceStatus::TIMEOUT_EXPIRED));
std::unordered_set<UboManager::FenceManager::AllocationId> reclaimedIds;
mFenceManager.reclaimCompletedResources(mDriverApi,
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.insert(id); });
EXPECT_TRUE(reclaimedIds.empty());
}
TEST_F(FenceManagerTest, ReclaimWhenFenceSignaled) {
mFenceManager.track(mDriverApi, { 10, 20 });
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(1)))
.WillOnce(Return(FenceStatus::CONDITION_SATISFIED));
std::unordered_set<UboManager::FenceManager::AllocationId> reclaimedIds;
mFenceManager.reclaimCompletedResources(mDriverApi,
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.insert(id); });
// Verify that the correct IDs were reclaimed.
ASSERT_EQ(reclaimedIds.size(), 2);
EXPECT_TRUE(reclaimedIds.contains(10));
EXPECT_TRUE(reclaimedIds.contains(20));
// Verify that the fence is no longer tracked by calling reclaim again.
reclaimedIds.clear();
EXPECT_CALL(mMockDriver, getFenceStatus(_)).Times(0); // No fences left to check.
mFenceManager.reclaimCompletedResources(mDriverApi,
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.insert(id); });
EXPECT_TRUE(reclaimedIds.empty());
}
TEST_F(FenceManagerTest, ReclaimMultipleFencesPartial) {
// Frame 1
mFenceManager.track(mDriverApi, { 1 });
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
// Frame 2
mFenceManager.track(mDriverApi, { 2 });
EXPECT_EQ(mMockDriver.nextFakeHandle, 3);
// Frame 3
mFenceManager.track(mDriverApi, { 3 });
EXPECT_EQ(mMockDriver.nextFakeHandle, 4);
// Now, reclaim. Assume fence 1 and 2 are done, but 3 is not.
// The implementation iterates from newest to oldest.
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(3)))
.WillOnce(Return(FenceStatus::TIMEOUT_EXPIRED));
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(2)))
.WillOnce(Return(FenceStatus::CONDITION_SATISFIED));
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(1)))
.WillOnce(Return(FenceStatus::CONDITION_SATISFIED));
std::vector<UboManager::FenceManager::AllocationId> reclaimedIds;
mFenceManager.reclaimCompletedResources(mDriverApi,
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.push_back(id); });
// Verify that resources from the first two frames were reclaimed.
ASSERT_EQ(reclaimedIds.size(), 2);
EXPECT_EQ(reclaimedIds[0], 1);
EXPECT_EQ(reclaimedIds[1], 2);
}
TEST_F(FenceManagerTest, Reset) {
mFenceManager.track(mDriverApi, { 10 });
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
mFenceManager.track(mDriverApi, { 20 });
EXPECT_EQ(mMockDriver.nextFakeHandle, 3);
mFenceManager.reset(mDriverApi);
// After reset, reclaiming should do nothing.
std::vector<UboManager::FenceManager::AllocationId> reclaimedIds;
EXPECT_CALL(mMockDriver, getFenceStatus(_)).Times(0);
mFenceManager.reclaimCompletedResources(mDriverApi,
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.push_back(id); });
EXPECT_TRUE(reclaimedIds.empty());
}
} // namespace anonymous
+366
View File
@@ -0,0 +1,366 @@
/*
* Copyright (C) 2025 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "MockDriver.h"
#include "details/MaterialInstance.h"
#include "details/UboManager.h"
#include <private/backend/CommandBufferQueue.h>
#include <private/backend/CommandStream.h>
#include <private/backend/Driver.h>
#include "filament_test_resources.h"
namespace {
using namespace filament;
using namespace backend;
using ::testing::NiceMock;
using AllocationId = BufferAllocator::AllocationId;
using allocation_size_t = BufferAllocator::allocation_size_t;
} // anonymous namespace
class UboManagerTest : public ::testing::Test {
protected:
static constexpr size_t MIN_COMMAND_BUFFERS_SIZE = 1 * 1024 * 1024;
static constexpr size_t COMMAND_BUFFERS_SIZE = 3 * MIN_COMMAND_BUFFERS_SIZE;
static constexpr BufferAllocator::allocation_size_t DEFAULT_SLOT_SIZE = 64;
static constexpr BufferAllocator::allocation_size_t DEFAULT_TOTAL_SIZE = 1024;
UboManagerTest()
: mCommandBufferQueue(MIN_COMMAND_BUFFERS_SIZE, COMMAND_BUFFERS_SIZE, false),
mCommandStream(mMockDriver, mCommandBufferQueue.getCircularBuffer()),
mDriverApi(mCommandStream),
// The constructor will call reallocate, which calls createBufferObject.
// MockDriver's default ...S() implementation returns an incrementing handle.
// So, the first handle will be 1.
mUboManager(mDriverApi, DEFAULT_SLOT_SIZE, DEFAULT_TOTAL_SIZE),
mPendingInstances(mUboManager.mPendingInstances),
mManagedInstances(mUboManager.mManagedInstances),
mUbHandle(mUboManager.mUbHandle),
mAllocator(mUboManager.mAllocator) {
mEngine = Engine::Builder()
.feature("material.enable_material_instance_uniform_batching", true)
.backend(Backend::NOOP)
.build();
mMaterial = Material::Builder()
.package(FILAMENT_TEST_RESOURCES_TEST_MATERIAL_DATA,
FILAMENT_TEST_RESOURCES_TEST_MATERIAL_SIZE)
.build(*mEngine);
}
void TearDown() override {
mEngine->destroy(mMaterial);
Engine::destroy(&mEngine);
}
// The engine is only for creating materials/material instances, we're not using the UboManager
// inside for testing.
Engine* mEngine = nullptr;
NiceMock<MockDriver> mMockDriver;
CommandBufferQueue mCommandBufferQueue;
CommandStream mCommandStream;
DriverApi& mDriverApi;
UboManager mUboManager;
Material const* mMaterial;
std::unordered_set<FMaterialInstance*>& mPendingInstances;
std::unordered_set<FMaterialInstance*>& mManagedInstances;
Handle<HwBufferObject>& mUbHandle;
BufferAllocator& mAllocator;
};
TEST_F(UboManagerTest, InitialState) {
EXPECT_EQ(mUboManager.getTotalSize(), DEFAULT_TOTAL_SIZE);
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
EXPECT_NE(mUbHandle.getId(), HandleBase::nullid);
}
TEST_F(UboManagerTest, BeginFrameWithoutReallocate) {
BufferAllocator::allocation_size_t originalBufferSize = mUboManager.getTotalSize();
auto mi1 = static_cast<FMaterialInstance*>(mMaterial->createInstance());
EXPECT_EQ(mi1->getAllocationId(), BufferAllocator::UNALLOCATED);
ASSERT_TRUE(mi1->isUsingUboBatching());
// The mi1 should be put in the pending list.
mUboManager.manageMaterialInstance(mi1);
EXPECT_TRUE(mPendingInstances.contains(mi1));
EXPECT_FALSE(mManagedInstances.contains(mi1));
mUboManager.beginFrame(mDriverApi);
// The mi1 should be moved to managed list after beginFrame.
EXPECT_FALSE(mPendingInstances.contains(mi1));
EXPECT_TRUE(mManagedInstances.contains(mi1));
// Should have allocation after beginFrame.
EXPECT_TRUE(BufferAllocator::isValid(mi1->getAllocationId()));
// Reallocation is not triggered under this case.
EXPECT_EQ(mUboManager.getTotalSize(), originalBufferSize);
EXPECT_NE(mUboManager.getMemoryMappedBufferHandle().getId(), HandleBase::nullid);
mUboManager.finishBeginFrame(mDriverApi);
EXPECT_EQ(mUboManager.getMemoryMappedBufferHandle().getId(), HandleBase::nullid);
mUboManager.endFrame(mDriverApi);
EXPECT_TRUE(mAllocator.isLockedByGpu(mi1->getAllocationId()));
// We're not using the UboManager inside mEngine, so we need to unmanage the material instance
// by ourselves.
mUboManager.unmanageMaterialInstance(mi1);
EXPECT_FALSE(mPendingInstances.contains(mi1));
EXPECT_FALSE(mManagedInstances.contains(mi1));
mUboManager.terminate(mDriverApi);
mEngine->destroy(mi1);
}
TEST_F(UboManagerTest, BeginFrameWithReallocate) {
const allocation_size_t originalBufferSize = mUboManager.getTotalSize();
const Handle<HwBufferObject> originalBufferHandle = mUbHandle;
// Create enough material instances to trigger a reallocation.
constexpr size_t numInstances = (DEFAULT_TOTAL_SIZE / DEFAULT_SLOT_SIZE) + 1;
std::vector<FMaterialInstance*> instances;
instances.reserve(numInstances);
for (size_t i = 0; i < numInstances; ++i) {
auto mi = static_cast<FMaterialInstance*>(mMaterial->createInstance());
instances.push_back(mi);
mUboManager.manageMaterialInstance(mi);
}
// Before beginFrame, all instances should be pending.
EXPECT_EQ(mPendingInstances.size(), numInstances);
EXPECT_TRUE(mManagedInstances.empty());
mUboManager.beginFrame(mDriverApi);
// After beginFrame, reallocation should have occurred.
EXPECT_NE(mUbHandle.getId(), originalBufferHandle.getId());
EXPECT_GT(mUboManager.getTotalSize(), originalBufferSize);
// All instances should now be managed and have valid allocations.
EXPECT_TRUE(mPendingInstances.empty());
EXPECT_EQ(mManagedInstances.size(), numInstances);
for (const auto* mi: instances) {
EXPECT_TRUE(mManagedInstances.contains(const_cast<FMaterialInstance*>(mi)));
EXPECT_TRUE(BufferAllocator::isValid(mi->getAllocationId()));
}
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.terminate(mDriverApi);
for (auto* mi: instances) {
// We're not using the UboManager inside mEngine, so we need to unmanage the material instance
// by ourselves.
mUboManager.unmanageMaterialInstance(mi);
mEngine->destroy(mi);
}
}
TEST_F(UboManagerTest, RecycleSlot) {
auto mi1 = static_cast<FMaterialInstance*>(mMaterial->createInstance());
mUboManager.manageMaterialInstance(mi1);
// Frame 1: mi1 gets an allocation.
mUboManager.beginFrame(mDriverApi);
const AllocationId mi1AllocationId = mi1->getAllocationId();
const allocation_size_t mi1AllocationOffset =
mAllocator.getAllocationOffset(mi1AllocationId);
EXPECT_TRUE(BufferAllocator::isValid(mi1AllocationId));
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.endFrame(mDriverApi); // Locks mi1's allocation.
// Now, unmanage mi1. The slot should be retired but not yet released.
mUboManager.unmanageMaterialInstance(mi1);
EXPECT_TRUE(mAllocator.isLockedByGpu(mi1AllocationId));
// Frame 2: The slot for mi1 is still locked by the GPU.
// We expect getFenceStatus to be called for the fence from frame 1.
// We'll mock it to return TIMEOUT_EXPIRED, so the resource is not reclaimed.
EXPECT_CALL(mMockDriver, getFenceStatus(_)).WillOnce(Return(FenceStatus::TIMEOUT_EXPIRED));
mUboManager.beginFrame(mDriverApi);
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.endFrame(mDriverApi);
// Frame 3: Now, we'll simulate that the fence from frame 1 has signaled.
// The resource for mi1 should be reclaimed.
EXPECT_CALL(mMockDriver, getFenceStatus(_)).WillOnce(Return(FenceStatus::CONDITION_SATISFIED));
auto mi2 = static_cast<FMaterialInstance*>(mMaterial->createInstance());
mUboManager.manageMaterialInstance(mi2);
mUboManager.beginFrame(mDriverApi);
// mi2 should now have a valid allocation, and it should reuse the slot from mi1.
EXPECT_TRUE(BufferAllocator::isValid(mi2->getAllocationId()));
EXPECT_EQ(mAllocator.getAllocationOffset(mi2->getAllocationId()), mi1AllocationOffset);
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.unmanageMaterialInstance(mi2);
mUboManager.terminate(mDriverApi);
mEngine->destroy(mi1);
mEngine->destroy(mi2);
}
TEST_F(UboManagerTest, OrphanSlot) {
auto mi1 = static_cast<FMaterialInstance*>(mMaterial->createInstance());
mUboManager.manageMaterialInstance(mi1);
// Frame 1: mi1 gets an allocation.
mUboManager.beginFrame(mDriverApi);
const AllocationId alloc1 = mi1->getAllocationId();
EXPECT_TRUE(BufferAllocator::isValid(alloc1));
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.endFrame(mDriverApi); // Locks alloc1.
// Frame 2: Mark the instance as dirty and begin a new frame.
// This should trigger orphaning.
mi1->getUniformBuffer().invalidate();
EXPECT_CALL(mMockDriver, getFenceStatus(_)).WillOnce(Return(FenceStatus::TIMEOUT_EXPIRED));
mUboManager.beginFrame(mDriverApi);
const AllocationId alloc2 = mi1->getAllocationId();
EXPECT_TRUE(BufferAllocator::isValid(alloc2));
EXPECT_NE(alloc1, alloc2); // Should have a new allocation.
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.endFrame(mDriverApi); // Locks alloc2.
// Frame 3: The fence for alloc1 should now be signaled.
EXPECT_CALL(mMockDriver, getFenceStatus(_))
.WillOnce(Return(FenceStatus::TIMEOUT_EXPIRED)) // For alloc2's fence
.WillOnce(Return(FenceStatus::CONDITION_SATISFIED)); // For alloc1's fence
mUboManager.beginFrame(mDriverApi);
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.unmanageMaterialInstance(mi1);
mUboManager.terminate(mDriverApi);
mEngine->destroy(mi1);
}
TEST_F(UboManagerTest, DoubleManage) {
auto mi1 = static_cast<FMaterialInstance*>(mMaterial->createInstance());
mUboManager.manageMaterialInstance(mi1);
EXPECT_EQ(mPendingInstances.size(), 1);
// Managing the same instance again should be a no-op.
mUboManager.manageMaterialInstance(mi1);
EXPECT_EQ(mPendingInstances.size(), 1);
mUboManager.terminate(mDriverApi);
mEngine->destroy(mi1);
}
TEST_F(UboManagerTest, ManageAndUnmanageBeforeBeginFrame) {
auto mi1 = static_cast<FMaterialInstance*>(mMaterial->createInstance());
mUboManager.manageMaterialInstance(mi1);
EXPECT_TRUE(mPendingInstances.contains(mi1));
mUboManager.unmanageMaterialInstance(mi1);
EXPECT_FALSE(mPendingInstances.contains(mi1));
// After beginFrame, the instance should not be in any list.
mUboManager.beginFrame(mDriverApi);
EXPECT_FALSE(mPendingInstances.contains(mi1));
EXPECT_FALSE(mManagedInstances.contains(mi1));
EXPECT_EQ(mi1->getAllocationId(), BufferAllocator::UNALLOCATED);
mUboManager.terminate(mDriverApi);
mEngine->destroy(mi1);
}
TEST_F(UboManagerTest, UnmanageUnmanaged) {
auto mi1 = static_cast<FMaterialInstance*>(mMaterial->createInstance());
// Unmanaging an instance that was never managed should not cause any issues.
mUboManager.unmanageMaterialInstance(mi1);
EXPECT_FALSE(mPendingInstances.contains(mi1));
EXPECT_FALSE(mManagedInstances.contains(mi1));
mUboManager.terminate(mDriverApi);
mEngine->destroy(mi1);
}
TEST_F(UboManagerTest, AllAllocationsLockedAfterEndFrame) {
constexpr size_t numInstances = 5;
std::vector<FMaterialInstance*> instances;
instances.reserve(numInstances);
for (size_t i = 0; i < numInstances; ++i) {
auto mi = static_cast<FMaterialInstance*>(mMaterial->createInstance());
instances.push_back(mi);
mUboManager.manageMaterialInstance(mi);
}
mUboManager.beginFrame(mDriverApi);
mUboManager.finishBeginFrame(mDriverApi);
mUboManager.endFrame(mDriverApi);
for (const auto* mi: instances) {
EXPECT_TRUE(mAllocator.isLockedByGpu(mi->getAllocationId()));
}
mUboManager.terminate(mDriverApi);
for (auto* mi: instances) {
// We're not using the UboManager inside mEngine, so we need to unmanage the material instance
// by ourselves.
mUboManager.unmanageMaterialInstance(mi);
mEngine->destroy(mi);
}
}
TEST_F(UboManagerTest, AllAllocationsLockedAfterEndFrameWithInvalidIdInBetween) {
constexpr size_t numInstances = 5;
std::vector<FMaterialInstance*> instances;
instances.reserve(numInstances);
for (size_t i = 0; i < numInstances; ++i) {
auto mi = static_cast<FMaterialInstance*>(mMaterial->createInstance());
instances.push_back(mi);
mUboManager.manageMaterialInstance(mi);
}
mUboManager.beginFrame(mDriverApi);
mUboManager.finishBeginFrame(mDriverApi);
// It should rarely happen, but we want to make sure all other instances are locked properly.
instances[2]->assignUboAllocation(mUbHandle, BufferAllocator::REALLOCATION_REQUIRED, 0);
mUboManager.endFrame(mDriverApi);
for (const auto* mi: instances) {
if (BufferAllocator::isValid(mi->getAllocationId())) {
EXPECT_TRUE(mAllocator.isLockedByGpu(mi->getAllocationId()));
}
}
mUboManager.terminate(mDriverApi);
for (auto* mi: instances) {
// We're not using the UboManager inside mEngine, so we need to unmanage the material instance
// by ourselves.
mUboManager.unmanageMaterialInstance(mi);
mEngine->destroy(mi);
}
}
// TODO: Add more tests for the beginFrame flow
Binary file not shown.
Binary file not shown.
@@ -28,9 +28,9 @@
namespace filament::descriptor_sets { namespace filament::descriptor_sets {
backend::DescriptorSetLayout const& getPostProcessLayout() noexcept;
backend::DescriptorSetLayout const& getDepthVariantLayout() noexcept; backend::DescriptorSetLayout const& getDepthVariantLayout() noexcept;
backend::DescriptorSetLayout const& getSsrVariantLayout() noexcept; backend::DescriptorSetLayout const& getSsrVariantLayout() noexcept;
backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept; backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept;
backend::DescriptorSetLayout getPerViewDescriptorSetLayout( backend::DescriptorSetLayout getPerViewDescriptorSetLayout(
+3 -16
View File
@@ -40,12 +40,7 @@ namespace filament::descriptor_sets {
using namespace backend; using namespace backend;
// used for post-processing passes // used to generate shadow-maps, structure and postfx passes
static constexpr std::initializer_list<DescriptorSetLayoutBinding> postProcessDescriptorSetLayoutList = {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
};
// used to generate shadow-maps
static constexpr std::initializer_list<DescriptorSetLayoutBinding> depthVariantDescriptorSetLayoutList = { static constexpr std::initializer_list<DescriptorSetLayoutBinding> depthVariantDescriptorSetLayoutList = {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
}; };
@@ -142,10 +137,6 @@ static const std::unordered_map<
{{ SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT }, DescriptorType::SAMPLER_EXTERNAL } {{ SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT }, DescriptorType::SAMPLER_EXTERNAL }
}; };
// used for post-processing passes
static DescriptorSetLayout const postProcessDescriptorSetLayout{ utils::StaticString("postProcess"),
postProcessDescriptorSetLayoutList };
// used to generate shadow-maps // used to generate shadow-maps
static DescriptorSetLayout const depthVariantDescriptorSetLayout{ static DescriptorSetLayout const depthVariantDescriptorSetLayout{
utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList
@@ -163,10 +154,6 @@ static DescriptorSetLayout perRenderableDescriptorSetLayout = {
utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList
}; };
DescriptorSetLayout const& getPostProcessLayout() noexcept {
return postProcessDescriptorSetLayout;
}
DescriptorSetLayout const& getDepthVariantLayout() noexcept { DescriptorSetLayout const& getDepthVariantLayout() noexcept {
return depthVariantDescriptorSetLayout; return depthVariantDescriptorSetLayout;
} }
@@ -280,10 +267,10 @@ DescriptorSetLayout getPerViewDescriptorSetLayout(
return layout; return layout;
} }
case MaterialDomain::POST_PROCESS: case MaterialDomain::POST_PROCESS:
return postProcessDescriptorSetLayout; return depthVariantDescriptorSetLayout;
case MaterialDomain::COMPUTE: case MaterialDomain::COMPUTE:
// TODO: what's the layout for compute? // TODO: what's the layout for compute?
return postProcessDescriptorSetLayout; return depthVariantDescriptorSetLayout;
} }
} }
+3 -3
View File
@@ -129,9 +129,9 @@ DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib
DescriptorSetLayout layout; DescriptorSetLayout layout;
layout.bindings.reserve(1 + samplers.size()); layout.bindings.reserve(1 + samplers.size());
layout.bindings.push_back(DescriptorSetLayoutBinding { DescriptorType::UNIFORM_BUFFER, layout.bindings.push_back(DescriptorSetLayoutBinding{ DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::NONE, 0 }); +PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::DYNAMIC_OFFSET, 0 });
for (auto const& sampler: samplers) { for (auto const& sampler: samplers) {
DescriptorSetLayoutBinding layoutBinding{ DescriptorSetLayoutBinding layoutBinding{
@@ -225,7 +225,7 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER)); f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER));
f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)); f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT));
f.writeUint8(0); f.writeUint8(0);
f.writeUint8(uint8_t(DescriptorFlags::NONE)); f.writeUint8(uint8_t(DescriptorFlags::DYNAMIC_OFFSET));
f.writeUint16(0); f.writeUint16(0);
// all the material's sampler descriptors // all the material's sampler descriptors
+7
View File
@@ -13,6 +13,11 @@ set(PUBLIC_HDRS
) )
set(SRCS set(SRCS
src/DirIncluder.h
src/DirIncluder.cpp
src/IncludeCallback.h
src/Includes.h
src/Includes.cpp
src/JsonishLexeme.h src/JsonishLexeme.h
src/JsonishLexer.h src/JsonishLexer.h
src/JsonishParser.h src/JsonishParser.h
@@ -53,6 +58,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/ DESTINATION include)
project(test_matp) project(test_matp)
set(TARGET test_matp) set(TARGET test_matp)
set(SRCS set(SRCS
tests/test_includes.cpp
tests/test_includer.cpp
tests/test_matp.cpp tests/test_matp.cpp
tests/TestMaterialParser.h tests/TestMaterialParser.h
tests/test_compute_material.cpp tests/test_compute_material.cpp
@@ -155,6 +155,9 @@ public:
return mWorkarounds; return mWorkarounds;
} }
bool getInsertLineDirectives() const noexcept { return mInsertLineDirectives; }
bool getInsertLineDirectiveChecks() const noexcept { return mInsertLineDirectiveChecks; }
protected: protected:
bool mDebug = false; bool mDebug = false;
bool mIsValid = true; bool mIsValid = true;
@@ -174,6 +177,8 @@ protected:
filament::UserVariantFilterMask mVariantFilter = 0; filament::UserVariantFilterMask mVariantFilter = 0;
Workarounds mWorkarounds = Workarounds::ALL; Workarounds mWorkarounds = Workarounds::ALL;
bool mIncludeEssl1 = true; bool mIncludeEssl1 = true;
bool mInsertLineDirectives = true;
bool mInsertLineDirectiveChecks = true;
}; };
} // namespace matp } // namespace matp
@@ -20,9 +20,11 @@
#include <unordered_map> #include <unordered_map>
#include <utils/Status.h>
#include "Config.h" #include "Config.h"
#include <utils/Path.h>
#include <utils/Status.h>
namespace filamat { namespace filamat {
class MaterialBuilder; class MaterialBuilder;
} }
@@ -36,7 +38,13 @@ class MaterialLexeme;
class MaterialParser { class MaterialParser {
public: public:
MaterialParser(); MaterialParser();
// Resolves #include directives in the material by looking at the given file structure.
// Returns the string of resolved material with the state that indicates whether the include
// resolution was successful.
std::pair<utils::Status, utils::CString> resolveIncludes(
const std::unique_ptr<const char[]>& buffer, ssize_t size,
const utils::Path& materialFilePath,
bool insertLineDirectives, bool insertLineDirectiveCheck);
// Parses a string material so that it can be used in MaterialBuilder. // Parses a string material so that it can be used in MaterialBuilder.
// Call MaterialBuilder::init before passing in the builder; call MaterialBuilder::build to // Call MaterialBuilder::init before passing in the builder; call MaterialBuilder::build to
// create filamat::Package after. // create filamat::Package after.
@@ -20,7 +20,7 @@
#include <fstream> #include <fstream>
namespace matc { namespace matp {
bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& result) { bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& result) {
auto getHeaderPath = [&result, &includedBy, this]() { auto getHeaderPath = [&result, &includedBy, this]() {
@@ -66,5 +66,5 @@ bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& re
return true; return true;
} }
} // namespace matc } // namespace matp
@@ -21,7 +21,7 @@
#include <utils/Path.h> #include <utils/Path.h>
namespace matc { namespace matp {
// Functor callback handler used to resolve includes relative to a root include directory. // Functor callback handler used to resolve includes relative to a root include directory.
class DirIncluder { class DirIncluder {
@@ -38,6 +38,6 @@ private:
}; };
} // namespace matc } // namespace matp
#endif #endif
@@ -18,10 +18,11 @@
#define TNT_INCLUDER_H #define TNT_INCLUDER_H
#include <utils/CString.h> #include <utils/CString.h>
#include <utils/Status.h>
#include <functional> #include <functional>
namespace matc { namespace matp {
struct IncludeResult { struct IncludeResult {
// The include name of the root file, as if it were being included. // The include name of the root file, as if it were being included.
@@ -30,7 +31,7 @@ struct IncludeResult {
const utils::CString includeName; const utils::CString includeName;
// The following fields should be filled out by the IncludeCallback when processing an include, // The following fields should be filled out by the IncludeCallback when processing an include,
// or when calling resolveIncludes for the root file. // or when calling resolveIncludesRecursively for the root file.
// The full contents of the include file. This may contain additional, recursive include // The full contents of the include file. This may contain additional, recursive include
// directives. // directives.
@@ -58,12 +59,12 @@ struct IncludeResult {
* @param result is the IncludeResult that the callback should fill out. * @param result is the IncludeResult that the callback should fill out.
* @return true, if the include was resolved successfully, false otherwise. * @return true, if the include was resolved successfully, false otherwise.
* *
* For an example of implementing this callback, see tools/matc/src/matc/DirIncluder.h. * For an example of implementing this callback, see DirIncluder.h.
*/ */
using IncludeCallback = std::function<bool( using IncludeCallback = std::function<bool(
const utils::CString& includedBy, const utils::CString& includedBy,
IncludeResult& result)>; IncludeResult& result)>;
} // namespace matc } // namespace matp
#endif #endif
@@ -19,10 +19,11 @@
#include <utils/Log.h> #include <utils/Log.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/sstream.h> #include <utils/sstream.h>
#include <utils/Status.h>
#include <string> #include <string>
namespace matc { namespace matp {
static bool isWhitespace(char c) { static bool isWhitespace(char c) {
return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'); return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v');
@@ -75,12 +76,11 @@ static void findCommentRanges(const char (&beginToken)[BTSize], const char (&end
} }
bool resolveIncludes(IncludeResult& root, IncludeCallback callback, utils::Status resolveIncludesRecursively(IncludeResult& root, IncludeCallback callback,
const ResolveOptions& options, size_t depth) { const ResolveOptions& options, size_t depth) {
if (depth > 30) { if (depth > 30) {
// This is probably an include cycle. Stop here and report an error so we don't overflow. // This is probably an include cycle. Stop here and report an error so we don't overflow.
utils::slog.e << "Include depth > 30. Include cycle?" << utils::io::endl; return utils::Status::invalidArgument("Include depth > 30. Include cycle?");
return false;
} }
utils::CString& text = root.text; utils::CString& text = root.text;
@@ -88,7 +88,7 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
std::vector<FoundInclude> includes = parseForIncludes(text); std::vector<FoundInclude> includes = parseForIncludes(text);
if (includes.empty()) { if (includes.empty()) {
// No more to resolve. // No more to resolve.
return true; return utils::Status::ok();
} }
const size_t lineNumberOffset = includes[0].line; const size_t lineNumberOffset = includes[0].line;
@@ -196,28 +196,30 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
const auto include = includes[0]; const auto include = includes[0];
// Ask the includer to resolve this include. // Ask the includer to resolve this include.
if (!callback) { if (!callback) {
return false; return utils::Status::invalidArgument("No callback is provided");
} }
IncludeResult resolved { IncludeResult includeResult{
.includeName = include.name .includeName = include.name
}; };
if (!callback(root.name, resolved)) { if (!callback(root.name, includeResult)) {
utils::slog.e << "The included file \"" << include.name.c_str() utils::io::sstream errorMessage;
errorMessage << "The included file \"" << include.name.c_str()
<< "\" could not be found." << utils::io::endl; << "\" could not be found." << utils::io::endl;
return false; return utils::Status::invalidArgument(errorMessage.c_str());
} }
// Recursively resolve all of its includes. // Recursively resolve all of its includes.
if (!resolveIncludes(resolved, callback, options, depth + 1)) { if (utils::Status resolveStatus =
return false; resolveIncludesRecursively(includeResult, callback, options, depth + 1);
!resolveStatus.isOk()) {
return resolveStatus;
} }
text.replace(include.startPosition, include.length, resolved.text); text.replace(include.startPosition, include.length, includeResult.text);
includes = parseForIncludes(text); includes = parseForIncludes(text);
} }
return utils::Status::ok();
return true;
} }
std::vector<FoundInclude> parseForIncludes(const utils::CString& source) { std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
@@ -308,4 +310,4 @@ std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
return results; return results;
} }
} // namespace matc } // namespace matp
@@ -20,10 +20,11 @@
#include "IncludeCallback.h" #include "IncludeCallback.h"
#include <utils/CString.h> #include <utils/CString.h>
#include <utils/Status.h>
#include <vector> #include <vector>
namespace matc { namespace matp {
struct ResolveOptions { struct ResolveOptions {
// If true, insert #line directives before / after each include. // If true, insert #line directives before / after each include.
@@ -39,9 +40,10 @@ struct ResolveOptions {
}; };
// Recursively handle all the includes inside of root. // Recursively handle all the includes inside of root.
// Returns true if all includes were handled successfully, false otherwise. // Returns utils::Status::StatusCode::OK if all includes were handled successfully, otherwise an
// callback may be null, in which case any #include directives found will result in a failure. // error status with a failure message.
bool resolveIncludes(IncludeResult& root, IncludeCallback callback, // Callback may be null, in which case any #include directives found will result in a failure.
utils::Status resolveIncludesRecursively(IncludeResult& root, IncludeCallback callback,
const ResolveOptions& options, size_t depth = 0); const ResolveOptions& options, size_t depth = 0);
struct FoundInclude { struct FoundInclude {
@@ -53,6 +55,6 @@ struct FoundInclude {
std::vector<FoundInclude> parseForIncludes(const utils::CString& source); std::vector<FoundInclude> parseForIncludes(const utils::CString& source);
} // namespace matc } // namespace matp
#endif #endif
+31
View File
@@ -16,6 +16,8 @@
#include <filament-matp/MaterialParser.h> #include <filament-matp/MaterialParser.h>
#include "DirIncluder.h"
#include "Includes.h"
#include "JsonishLexer.h" #include "JsonishLexer.h"
#include "JsonishParser.h" #include "JsonishParser.h"
#include "MaterialLexeme.h" #include "MaterialLexeme.h"
@@ -393,6 +395,35 @@ utils::Status MaterialParser::processMaterialParameters(filamat::MaterialBuilder
return status; return status;
} }
std::pair<utils::Status, utils::CString> MaterialParser::resolveIncludes(
const std::unique_ptr<const char[]>& buffer, ssize_t size,
const utils::Path& materialFilePath,
bool insertLineDirectives, bool insertLineDirectiveCheck) {
ResolveOptions const options {
.insertLineDirectives = insertLineDirectives,
.insertLineDirectiveCheck = insertLineDirectiveCheck
};
// Inline material (i.e. resolve #include directives)
DirIncluder includer;
includer.setIncludeDirectory(materialFilePath.getParent());
utils::CString fileName = utils::CString(materialFilePath.getName().c_str());
// This is both the source and the result.
// This will be mutated by the below `resolveIncludesRecursively` call.
IncludeResult result {
.includeName = fileName,
.text = utils::CString(buffer.get(), size),
.name = CString("")
};
utils::Status resolveStatus =
matp::resolveIncludesRecursively(result, std::move(includer), options);
return { resolveStatus, result.text };
}
utils::Status MaterialParser::processTemplateSubstitutions( utils::Status MaterialParser::processTemplateSubstitutions(
const Config& config, ssize_t& size, std::unique_ptr<const char[]>& buffer) { const Config& config, ssize_t& size, std::unique_ptr<const char[]>& buffer) {
const auto& templateMap = config.getTemplateMap(); const auto& templateMap = config.getTemplateMap();
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include <matc/IncludeCallback.h> #include "IncludeCallback.h"
#include <utils/CString.h> #include <utils/CString.h>
@@ -37,7 +37,7 @@ public:
return *this; return *this;
} }
bool operator()(const utils::CString& includedBy, matc::IncludeResult& result) { bool operator()(const utils::CString& includedBy, matp::IncludeResult& result) {
auto key = result.includeName.c_str(); auto key = result.includeName.c_str();
auto found = mIncludeMap.find(key); auto found = mIncludeMap.find(key);

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