Compare commits

..
Author SHA1 Message Date
Run Yu 8c1167ddb8 Use GetMappedRange early but still get null pointer sometimes 2025-11-21 13:34:57 -05:00
Run Yu c9b5bfc4a4 Use a stage pool for WebGPU, but somehow GetMappedRange would sometimes return null, which breaks the code. 2025-11-21 10:57:02 -05: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
295 changed files with 22058 additions and 8475 deletions
+1 -1
View File
@@ -179,7 +179,7 @@ jobs:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- 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
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
[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
- 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);
}
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
Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) {
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)
static float[] assertFloat4(@Nullable float[] out) {
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.assertFloat4In;
import static com.google.android.filament.Colors.LinearColor;
import com.google.android.filament.proguard.UsedByNative;
@@ -674,6 +673,21 @@ public class View {
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).
*
@@ -1317,6 +1331,7 @@ public class View {
private static native void nSetDithering(long nativeView, int dithering);
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 nGetLastDynamicResolutionScale(long nativeView, float[] out);
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
private static native void nSetShadowType(long nativeView, int type);
@@ -40,7 +40,21 @@ fun loadTexture(engine: Engine, resources: Resources, resourceId: Int, type: Tex
options.inPremultiplied = type == TextureType.COLOR
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()
.width(bitmap.width)
.height(bitmap.height)
@@ -22,6 +22,7 @@ object Utils {
/**
* Initializes the utils JNI layer. Must be called before using any utils functionality.
*/
@JvmStatic
fun init() {
// Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv.
Filament.init()
@@ -17,12 +17,20 @@ filamentTools {
}
// 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"
into "src/main/assets/models"
}
preBuild.dependsOn copyMesh
preBuild.dependsOn copyDamagedHelmetGltf
preBuild.dependsOn copyBusterGltf
clean.doFirst {
delete "src/main/assets"
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.66.2'
implementation 'com.google.android.filament:filament-android:1.67.1'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<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>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<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
+16 -3
View File
@@ -252,10 +252,21 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/utils/Spirv.h
src/vulkan/utils/StaticVector.h
)
if (LINUX OR WIN32)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
if (LINUX)
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)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformApple.mm)
list(APPEND SRCS
include/backend/platforms/VulkanPlatformApple.h
src/vulkan/platform/VulkanPlatformApple.mm
)
elseif (ANDROID)
list(APPEND SRCS
include/backend/platforms/VulkanPlatformAndroid.h
@@ -305,6 +316,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPURenderPrimitive.h
src/webgpu/WebGPURenderTarget.cpp
src/webgpu/WebGPURenderTarget.h
src/webgpu/WebGPUStagePool.cpp
src/webgpu/WebGPUStagePool.h
src/webgpu/WebGPUStrings.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
+37 -7
View File
@@ -19,13 +19,17 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
#define TNT_FILAMENT_BACKEND_PLATFORM_H
#include <utils/CString.h>
#include <utils/compiler.h>
#include <utils/Invocable.h>
#include <utils/Mutex.h>
#include <stddef.h>
#include <stdint.h>
#include <atomic>
#include <memory>
#include <mutex>
namespace filament::backend {
@@ -508,13 +512,22 @@ public:
// --------------------------------------------------------------------------------------------
// 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
* to aid with debugging. This callback is guaranteed to be called on the Filament driver
* 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
*/
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
* thread.
*
* @param key a null-terminated C-string with the key of the debug statistic
* @param value the updated value of key
* @param key a null-terminated C-string with the key of the debug statistic
* @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:
InsertBlobFunc mInsertBlob;
RetrieveBlobFunc mRetrieveBlob;
DebugUpdateStatFunc mDebugUpdateStat;
std::shared_ptr<InsertBlobFunc> mInsertBlob;
std::shared_ptr<RetrieveBlobFunc> mRetrieveBlob;
std::shared_ptr<DebugUpdateStatFunc> mDebugUpdateStat;
mutable utils::Mutex mMutex;
};
} // namespace filament
@@ -17,8 +17,6 @@
#ifndef 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 <backend/AcquiredImage.h>
@@ -171,19 +169,10 @@ protected:
SwapChain* drawSwapChain,
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:
struct SwapChainEGLAndroid;
struct AndroidDetails;
// prevent derived classes' implementations to call through
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
@@ -203,17 +192,14 @@ private:
int mOSVersion;
ExternalStreamManagerAndroid& mExternalStreamManager;
AndroidDetails& mAndroidDetails;
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
using clock = std::chrono::high_resolution_clock;
clock::time_point mStartTimeOfActualWork;
AndroidProducerThrottling mProducerThrottling;
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
bool mAssertNativeWindowIsValid = false;
AndroidFrameCallback mAndroidFrameCallback;
};
} // namespace filament::backend
@@ -437,22 +437,19 @@ protected:
std::shared_ptr<VulkanCmdFence> fenceStatus;
};
virtual ExtensionSet getSwapchainInstanceExtensions() const;
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
virtual ExtensionSet getSwapchainInstanceExtensions() const = 0;
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept;
uint64_t flags) const noexcept = 0;
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
/**
* Query if transient attachments are supported by the backend.
*/
bool isTransientAttachmentSupported() const noexcept;
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;
};
@@ -17,12 +17,13 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
#include <utils/compiler.h>
#include <android/hardware_buffer.h>
namespace filament::backend {
@@ -91,6 +92,8 @@ protected:
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
private:
struct AndroidDetails;
struct ExternalImageVulkanAndroid : public ExternalImage {
AHardwareBuffer* aHardwareBuffer = nullptr;
bool sRGB = false;
@@ -99,7 +102,7 @@ private:
~ExternalImageVulkanAndroid() override;
};
AndroidFrameCallback mAndroidFrameCallback;
AndroidDetails& mAndroidDetails;
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
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include "AndroidNdk.h"
#include <backend/platforms/AndroidNdk.h>
#include <android/hardware_buffer.h>
+42 -11
View File
@@ -139,42 +139,73 @@ bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*
}
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
mInsertBlob = std::move(insertBlob);
mRetrieveBlob = std::move(retrieveBlob);
std::lock_guard<decltype(mMutex)> lock(mMutex);
mInsertBlob = std::make_shared<InsertBlobFunc>(std::move(insertBlob));
mRetrieveBlob = std::make_shared<RetrieveBlobFunc>(std::move(retrieveBlob));
}
bool Platform::hasInsertBlobFunc() const noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
return bool(mInsertBlob);
}
bool Platform::hasRetrieveBlobFunc() const noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
return bool(mRetrieveBlob);
}
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
if (mInsertBlob) {
mInsertBlob(key, keySize, value, valueSize);
std::shared_ptr<InsertBlobFunc> callback;
{
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) {
if (mRetrieveBlob) {
return mRetrieveBlob(key, keySize, value, valueSize);
std::shared_ptr<RetrieveBlobFunc> callback;
{
std::unique_lock<decltype(mMutex)> lock(mMutex);
callback = mRetrieveBlob;
}
if (callback) {
return (*callback)(key, keySize, value, valueSize);
}
return 0;
}
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 {
return bool(mDebugUpdateStat);
std::lock_guard<decltype(mMutex)> lock(mMutex);
return mDebugUpdateStat != nullptr;
}
void Platform::debugUpdateStat(const char* key, uint64_t value) {
if (mDebugUpdateStat) {
mDebugUpdateStat(key, value);
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
std::shared_ptr<DebugUpdateStatFunc> callback;
{
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(__ANDROID__)
#include "backend/platforms/VulkanPlatformAndroid.h"
#else
#include "backend/platforms/VulkanPlatform.h"
#elif defined(__APPLE__)
#include "backend/platforms/VulkanPlatformApple.h"
#elif defined(__linux__)
#include "backend/platforms/VulkanPlatformLinux.h"
#elif defined(WIN32)
#include "backend/platforms/VulkanPlatformWindows.h"
#endif
#endif
@@ -117,8 +121,14 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#if defined(__ANDROID__)
return new VulkanPlatformAndroid();
#elif defined(__APPLE__)
return new VulkanPlatformApple();
#elif defined(__linux__)
return new VulkanPlatformLinux();
#elif defined(WIN32)
return new VulkanPlatformWindows();
#else
return new VulkanPlatform();
return nullptr;
#endif
#else
return nullptr;
@@ -17,6 +17,7 @@
#ifndef TNT_METALCONTEXT_H
#define TNT_METALCONTEXT_H
#include "MetalErrorQueue.h"
#include "MetalResourceTracker.h"
#include "MetalShaderCompiler.h"
#include "MetalState.h"
@@ -129,6 +130,7 @@ struct MetalContext {
id<MTLCommandBuffer> pendingCommandBuffer = nil;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
uint32_t currentFrame = 0;
MetalErrorQueue commandBufferErrors;
std::atomic<bool> memorylessLimitsReached = false;
@@ -153,6 +153,7 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
logMTLCommandBufferError(errorCode);
context->commandBufferErrors.push(buffer.error);
}
}];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
+12
View File
@@ -254,6 +254,18 @@ MetalDriver::~MetalDriver() noexcept {
void MetalDriver::tick(int) {
executeTickOps();
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,
@@ -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
@@ -28,14 +28,13 @@
#include <private/backend/VirtualMachineEnv.h>
#include "AndroidNativeWindow.h"
#include "AndroidFrameCallback.h"
#include "AndroidSwapChainHelper.h"
#include "ExternalStreamManagerAndroid.h"
#include <android/api-level.h>
#include <android/native_window.h>
#include <android/hardware_buffer.h>
#include <android/choreographer.h>
#include <android/looper.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
@@ -99,6 +98,25 @@ 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;
private:
AndroidSwapChainHelper mImpl{};
};
struct PlatformEGLAndroid::AndroidDetails {
AndroidProducerThrottling producerThrottling;
AndroidFrameCallback androidFrameCallback;
};
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
// to attach this thread to the JVM before we initialize PerformanceHintManager.
@@ -112,17 +130,20 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) {
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
}
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
delete &mAndroidDetails;
}
void PlatformEGLAndroid::terminate() noexcept {
mAndroidFrameCallback.terminate();
mAndroidDetails.androidFrameCallback.terminate();
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
PlatformEGL::terminate();
}
@@ -240,7 +261,7 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
mAndroidFrameCallback.init();
mAndroidDetails.androidFrameCallback.init();
return driver;
}
@@ -264,7 +285,7 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
}
AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidFrameCallback.getPreferredTimeline() };
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
@@ -675,12 +696,12 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
return mProducerThrottling.isSupported();
return mAndroidDetails.producerThrottling.isSupported();
}
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
EGLNativeWindowType const nativeWindow, bool const enabled) const {
return mProducerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
return mAndroidDetails.producerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
}
// ---------------------------------------------------------------------------------------------
@@ -214,4 +214,10 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
// destroying any unused pipeline object.
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
@@ -154,6 +154,10 @@ public:
return mStagingBufferBypassEnabled;
}
inline bool pipelineCreationFeedbackSupported() const noexcept {
return mPipelineCreationFeedbackSupported;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -181,6 +185,7 @@ private:
bool mProtectedMemorySupported = false;
bool mIsUnifiedMemoryArchitecture = false;
bool mStagingBufferBypassEnabled = false;
bool mPipelineCreationFeedbackSupported = false;
fvkutils::VkFormatList mDepthStencilFormats;
fvkutils::VkFormatList mBlittableDepthStencilFormats;
+1 -9
View File
@@ -231,7 +231,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
mPipelineLayoutCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice(), mContext),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(mContext, mResourceManager, mAllocator),
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,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
swapChain.inc();
@@ -48,10 +48,6 @@
#include <utils/Allocator.h>
#include <utils/compiler.h>
#if defined(__ANDROID__)
#include "AndroidNativeWindow.h"
#endif
namespace filament::backend {
class VulkanPlatform;
@@ -206,9 +202,6 @@ private:
bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported;
backend::StereoscopicType const mStereoscopicType;
#if defined(__ANDROID__)
AndroidProducerThrottling mProducerThrottling;
#endif
};
} // namespace filament::backend
@@ -34,8 +34,41 @@ using namespace bluevk;
namespace filament::backend {
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
: mDevice(device) {
namespace {
#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 = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
};
@@ -216,15 +249,40 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
}
}
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
<< shaderStages[0].module << ", " << shaderStages[1].module << ")";
#endif
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[1].module << ")";
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 = {
.lastUsed = mCurrentTime,
};
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
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);
if (error != VK_SUCCESS) {
FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
@@ -86,7 +86,7 @@ public:
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;
@@ -210,6 +210,8 @@ private:
// Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {};
[[maybe_unused]] VulkanContext const& mContext;
};
} // namespace filament::backend
@@ -228,6 +228,10 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
#endif
VK_KHR_MULTIVIEW_EXTENSION_NAME,
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME,
#endif
};
ExtensionSet exts;
// Identify supported physical device extensions
@@ -865,6 +869,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mPipelineCreationFeedbackSupported =
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
} else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
@@ -873,13 +879,16 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
// Check the availability of lazily allocated memory
context.mLazilyAllocatedMemorySupported = false;
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
++i) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
// RenderDoc doesn't support lazy allocated memory
if constexpr (!FVK_RENDERDOC_CAPTURE_MODE) {
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
++i) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
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);
}
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
return getSwapchainInstanceExtensionsImpl();
bool VulkanPlatform::isTransientAttachmentSupported() const noexcept {
return mImpl->mContext.isLazilyAllocatedMemorySupported();
}
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) const noexcept {
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
}
}// namespace filament::backend
} // namespace filament::backend
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <backend/platforms/VulkanPlatformAndroid.h>
#include "vulkan/VulkanConstants.h"
@@ -20,6 +21,8 @@
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
#include "vulkan/utils/Image.h"
#include "AndroidFrameCallback.h"
#include <private/backend/BackendUtilsAndroid.h>
#include <backend/DriverEnums.h>
@@ -32,6 +35,7 @@
#include <android/hardware_buffer.h>
#include <android/native_window.h>
#include <new>
#include <utility>
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() {
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();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
}
}
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept = default;
VulkanPlatformAndroid::~VulkanPlatformAndroid() noexcept {
delete &mAndroidDetails;
}
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
AHardwareBuffer const* buffer, bool sRGB) noexcept {
@@ -354,8 +411,6 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
.image = image,
.buffer = VK_NULL_HANDLE,
};
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
VkMemoryPropertyFlags requiredMemoryFlags =
!isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
@@ -365,8 +420,8 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
}
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
metadata.memoryTypeBits, requiredMemoryFlags);
uint32_t const memoryTypeIndex = selectMemoryTypeForExternalImage(physicalDevice, device,
image, metadata.memoryTypeBits, requiredMemoryFlags);
VkMemoryAllocateInfo const allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
@@ -459,14 +514,14 @@ int VulkanPlatformAndroid::getOSVersion() const noexcept {
}
void VulkanPlatformAndroid::terminate() {
mAndroidFrameCallback.terminate();
mAndroidDetails.androidFrameCallback.terminate();
VulkanPlatform::terminate();
}
Driver* VulkanPlatformAndroid::createDriver(void* sharedContext, DriverConfig const& driverConfig) {
Driver* driver = VulkanPlatform::createDriver(sharedContext, driverConfig);
if (driver) {
mAndroidFrameCallback.init();
mAndroidDetails.androidFrameCallback.init();
}
return driver;
}
@@ -482,7 +537,7 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
}
AndroidFrameCallback::Timeline const preferredTimeline{
mAndroidFrameCallback.getPreferredTimeline() };
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
outCompositorTiming->frameTime = preferredTimeline.frameTime;
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
@@ -502,13 +557,4 @@ bool VulkanPlatformAndroid::queryFrameTimestamps(SwapChain const* swapchain, uin
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
@@ -14,10 +14,9 @@
* limitations under the License.
*/
#include <backend/platforms/VulkanPlatform.h>
#include <backend/platforms/VulkanPlatformApple.h>
#include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanDriverFactory.h"
#include <utils/Panic.h>
@@ -36,15 +35,15 @@ using namespace bluevk;
namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
VulkanPlatform::ExtensionSet VulkanPlatformApple::getSwapchainInstanceExtensions() const {
ExtensionSet const ret = {
VK_EXT_METAL_SURFACE_EXTENSION_NAME,
};
return ret;
}
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept {
VulkanPlatform::SurfaceBundle VulkanPlatformApple::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) const noexcept {
VkSurfaceKHR surface;
CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow;
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");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
#include <backend/platforms/VulkanPlatform.h>
#include <backend/platforms/VulkanPlatformLinux.h>
#include <backend/DriverEnums.h>
#include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanDriverFactory.h"
#include <utils/Panic.h>
@@ -74,17 +73,13 @@
void* library = nullptr;
} g_x11_vk;
}// anonymous namespace
#elif defined(WIN32)
// No platform specific includes
#else
// Not a supported Vulkan platform
#endif
using namespace bluevk;
namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
VulkanPlatform::ExtensionSet VulkanPlatformLinux::getSwapchainInstanceExtensions() const {
VulkanPlatform::ExtensionSet const ret = {
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
@@ -95,15 +90,13 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
#if defined(FILAMENT_SUPPORTS_XLIB)
VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
#endif
#elif defined(WIN32)
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
#endif
};
return ret;
}
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept {
VulkanPlatform::SurfaceBundle VulkanPlatformLinux::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) const noexcept {
VkSurfaceKHR surface;
// 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);
}
#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
return std::make_tuple(surface, extent);
}
@@ -165,6 +165,11 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
VkResult VulkanPlatformSurfaceSwapChain::create() {
#ifdef __ANDROID__
NativeWindow::enableFrameTimestamps(static_cast<ANativeWindow*>(mNativeWindow), true);
// on Android, disable producer throttling
if (mProducerThrottling.isSupported()) {
mProducerThrottling.setProducerThrottlingEnabled(
static_cast<ANativeWindow*>(mNativeWindow), false);
}
#endif
VkSurfaceFormatKHR surfaceFormat = {};
@@ -25,7 +25,8 @@
#include <bluevk/BlueVK.h>
#ifdef __ANDROID__
#include <AndroidSwapChainHelper.h>
#include "AndroidSwapChainHelper.h"
#include "AndroidNativeWindow.h"
#endif
#include <unordered_map>
@@ -122,6 +123,7 @@ private:
#ifdef __ANDROID__
AndroidSwapChainHelper mImpl{};
AndroidProducerThrottling mProducerThrottling;
#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
@@ -18,6 +18,7 @@
#include "WebGPUConstants.h"
#include "WebGPUQueueManager.h"
#include "WebGPUStagePool.h"
#include "DriverBase.h"
#include <backend/BufferDescriptor.h>
@@ -29,6 +30,7 @@
#include <cstdint>
#include <cstring>
#include <iostream>
namespace filament::backend {
@@ -65,7 +67,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
// of 4 by padding with zeros.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager) {
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "updateGPUBuffer called with a null buffer";
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
@@ -79,34 +81,70 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
// This may have some performance implications. That should be investigated later.
assert_invariant(mBuffer.GetUsage() & wgpu::BufferUsage::CopyDst);
// Calculate some alignment related sizes
// // Calculate some alignment related sizes
const size_t remainder = bufferDescriptor.size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
const size_t mainBulk = bufferDescriptor.size - remainder;
const size_t stagingBufferSize =
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
//
// // create a staging buffer
// wgpu::BufferDescriptor descriptor{
// .label = "Filament WebGPU Staging Buffer",
// .usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
// .size = stagingBufferSize,
// .mappedAtCreation = true };
// wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
MappedStage mappedStage = webGPUStagePool->acquireBuffer(stagingBufferSize);
// create a staging buffer
wgpu::BufferDescriptor descriptor{
.label = "Filament WebGPU Staging Buffer",
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
.size = stagingBufferSize,
.mappedAtCreation = true };
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
void* mappedRange = stagingBuffer.GetMappedRange();
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
std::string mappedRangeIsNull = mappedStage.mappedRange
? "no"
: "yes";
std::cout << "Run Yu: got mapped range on the staging buffer with size "
<< mappedStage.buffer.GetSize() << " and it is null? " << mappedRangeIsNull << std::endl;
memcpy(mappedStage.mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
// Make sure the padded memory is set to 0 to have deterministic behaviors
if (remainder != 0) {
uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
}
// if (remainder != 0) {
// uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
// memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
// }
// size_t stagingBufferSize = stagingBuffer.GetSize();
// if (stagingBufferSize != bufferDescriptor.size) {
// assert(stagingBufferSize > bufferDescriptor.size);
// assert(stagingBufferSize % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS == 0);
// uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
// memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - (stagingBuffer.GetSize() - bufferDescriptor.size));
// }
stagingBuffer.Unmap();
mappedStage.buffer.Unmap();
std::cout << "Run Yu: about to issue copy command with actual staging buffer of size "
<< mappedStage.buffer.GetSize() << ", and computed size of " << stagingBufferSize
<< ". The mBuffer size is " << mBuffer.GetSize() << std::endl;
// Copy the staging buffer contents to the destination buffer.
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
byteOffset, stagingBufferSize);
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(mappedStage.buffer, 0, mBuffer,
byteOffset,
remainder == 0 ? bufferDescriptor.size
: mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
webGPUQueueManager->flush();
struct UserData final {
wgpu::Buffer stagingBuffer;
WebGPUStagePool* webGPUStagePool;
};
auto userData = std::make_unique<UserData>(
UserData{ .stagingBuffer = mappedStage.buffer, .webGPUStagePool = webGPUStagePool });
mappedStage.buffer.MapAsync(
wgpu::MapMode::Write, 0, stagingBufferSize, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userData) {
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userData));
userData->webGPUStagePool->addBufferToPool(userData->stagingBuffer);
} else {
std::cout << "Run Yu: MAPPING UNSUCCESSFUL!!\n";
}
},
userData.release());
}
} // namespace filament::backend
@@ -25,6 +25,7 @@ namespace filament::backend {
class BufferDescriptor;
class WebGPUQueueManager;
class WebGPUStagePool;
/**
* A base class for WebGPU buffer objects, providing common functionality for creating and
@@ -40,7 +41,7 @@ public:
* ensures the calls happen in the expected sequence.
*/
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager);
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool);
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
+4 -3
View File
@@ -107,6 +107,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueueManager{ mDevice },
mStagePool{ mDevice },
mPipelineLayoutCache{ mDevice },
mPipelineCache{ mDevice },
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
@@ -856,7 +857,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
// draw calls are made.
flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -867,14 +868,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
// draw calls are made.
flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -25,6 +25,7 @@
#include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include "webgpu/WebGPUQueueManager.h"
#include "webgpu/WebGPUStagePool.h"
#include "webgpu/utils/AsyncTaskCounter.h"
#include <backend/platforms/WebGPUPlatform.h>
@@ -81,6 +82,7 @@ private:
wgpu::Device mDevice = nullptr;
wgpu::Limits mDeviceLimits = {};
WebGPUQueueManager mQueueManager;
WebGPUStagePool mStagePool;
void* mNativeWindow = nullptr;
WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;
@@ -0,0 +1,88 @@
/*
* 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 "WebGPUStagePool.h"
#include "WebGPUConstants.h"
#include <iostream>
namespace filament::backend {
WebGPUStagePool::WebGPUStagePool(wgpu::Device const& device) : mDevice(device) {}
WebGPUStagePool::~WebGPUStagePool() = default;
MappedStage WebGPUStagePool::acquireBuffer(size_t requiredSize) {
std::cout << "Run Yu: required size in acquireBuffer: " << requiredSize << std::endl;
std::cout << "Run Yu: the pool size is " << mBuffers.size() << std::endl;
{
std::lock_guard<std::mutex> lock(mMutex);
auto iter = mBuffers.lower_bound(requiredSize);
if (iter != mBuffers.end()) {
std::cout << "Run Yu: found buffer in the pool with size " << iter->second.GetSize()
<< std::endl;
if (iter->second.GetMapState() != wgpu::BufferMapState::Mapped) {
std::cout << "Run Yu: before GetMappedRange the buffer state is not mapped!\n";
}
MappedStage mappedStage = { .buffer = iter->second,
.mappedRange = iter->second.GetMappedRange() };
if (!mappedStage.mappedRange) {
std::cout << "Run Yu: mapped range is null in acquireBuffer!\n";
}
if (mappedStage.buffer.GetMapState() != wgpu::BufferMapState::Mapped) {
std::cout << "Run Yu: after GetMappedRange the buffer state is not mapped!\n";
}
mBuffers.erase(iter);
return mappedStage;
}
}
wgpu::Buffer newBuffer = createNewBuffer(requiredSize);
return { .buffer = newBuffer, .mappedRange = newBuffer.GetMappedRange() };
}
void WebGPUStagePool::addBufferToPool(wgpu::Buffer buffer) {
std::lock_guard<std::mutex> lock(mMutex);
std::cout << "Run Yu: adding buffer to the pool with size " << buffer.GetSize() << std::endl;
mBuffers.insert({buffer.GetSize(), buffer});
std::cout << "Run Yu: added buffer to the pool with size " << buffer.GetSize() << std::endl;
bool allMapped = true;
for (const auto& pair : mBuffers) {
auto state = pair.second.GetMapState();
if (state != wgpu::BufferMapState::Mapped) {
allMapped = false;
std::cout << "Run Yu: the buffer with size " << pair.second.GetSize()
<< " is not mapped but somehow was added to the pool, its state is "
<< static_cast<int>(state) << std::endl;
}
}
if (!allMapped) {
std::cout << "Run Yu: found buffers that are not mapped\n";
}
}
wgpu::Buffer WebGPUStagePool::createNewBuffer(size_t bufferSize) {
std::cout << "Run Yu: creating new buffer with size " << bufferSize << std::endl;
wgpu::BufferDescriptor descriptor{
.label = "Filament WebGPU Staging Buffer",
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
.size = bufferSize,
.mappedAtCreation = true };
return mDevice.CreateBuffer(&descriptor);
}
} // namespace filament::backend
@@ -0,0 +1,49 @@
/*
* 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_WEBGPUSTAGEPOOL_H
#define TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
#include <webgpu/webgpu_cpp.h>
#include <map>
#include <mutex>
namespace filament::backend {
struct MappedStage {
wgpu::Buffer buffer;
void* mappedRange;
};
class WebGPUStagePool {
public:
WebGPUStagePool(wgpu::Device const& device);
~WebGPUStagePool();
MappedStage acquireBuffer(size_t requiredSize);
void addBufferToPool(wgpu::Buffer buffer);
private:
wgpu::Buffer createNewBuffer(size_t bufferSize);
std::multimap<uint32_t, wgpu::Buffer> mBuffers;
mutable std::mutex mMutex;
wgpu::Device mDevice;
};
}
#endif // TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
+11 -11
View File
@@ -243,10 +243,10 @@ public:
/** Utility to set the projection matrix from the field-of-view.
*
* @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180.
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near.
* @param direction direction of the \p fovInDegrees parameter.
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near.
* @param direction direction of the \p fovInDegrees parameter.
*
* @see Fov.
*/
@@ -256,9 +256,9 @@ public:
/** Utility to set the projection matrix from the focal length.
*
* @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0.
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @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 aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near.
*/
void setLensProjection(double focalLengthInMillimeters,
double aspect, double near, double far);
@@ -270,8 +270,8 @@ public:
* that is all 3 axis are mapped to [-1, 1].
*
* @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 far distance in world units from the camera to the far plane. \p far > \p near.
* @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.
*/
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 projectionForCulling custom projection matrix used for culling
* @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near.
* @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.
*/
void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling,
double near, double far) noexcept;
+8
View File
@@ -541,6 +541,14 @@ public:
*/
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
* information about the different settings available.
+28 -5
View File
@@ -16,14 +16,30 @@
#include "details/Camera.h"
#include <filament/Camera.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 {
using namespace math;
void Camera::setProjection(double const fovInDegrees, double const aspect, double const near, double const far,
Fov const direction) {
void Camera::setProjection(double const fovInDegrees, double const aspect,
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(
projection(direction, fovInDegrees, aspect, near),
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,
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(
projection(focalLengthInMillimeters, aspect, near),
projection(focalLengthInMillimeters, aspect, near, far),
@@ -149,8 +170,9 @@ utils::Entity Camera::getEntity() const noexcept {
return downcast(this)->getEntity();
}
void Camera::setExposure(float const aperture, float const shutterSpeed, float const ISO) noexcept {
downcast(this)->setExposure(aperture, shutterSpeed, ISO);
void Camera::setExposure(
float const aperture, float const shutterSpeed, float const sensitivity) noexcept {
downcast(this)->setExposure(aperture, shutterSpeed, sensitivity);
}
float Camera::getAperture() const noexcept {
@@ -177,7 +199,8 @@ double Camera::getFocalLength() const noexcept {
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);
}
+24 -2
View File
@@ -226,6 +226,8 @@ bool Froxelizer::prepare(
filament::Viewport const& viewport,
const mat4f& projection, float const projectionNear, float const projectionFar,
float4 const& clipTransform) noexcept {
assert_invariant(projectionFar > projectionNear);
assert_invariant(projectionNear > 0);
setViewport(viewport);
setProjection(projection, projectionNear, projectionFar);
@@ -383,8 +385,28 @@ bool Froxelizer::update() noexcept {
bool uniformsNeedUpdating = false;
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);
if (zLightFar != mZLightFar || zLightNear != mZLightNear) {
mDirtyFlags |= VIEWPORT_CHANGED;
+23 -19
View File
@@ -258,6 +258,10 @@ void PostProcessManager::bindPerRenderableDescriptorSet(DriverApi& driver) const
{ { 0, 0 }, driver });
}
UboManager* PostProcessManager::getUboManager() const noexcept {
return mEngine.getUboManager();
}
UTILS_NOINLINE
void PostProcessManager::registerPostProcessMaterial(std::string_view const name,
StaticMaterialInfo const& info) {
@@ -513,7 +517,7 @@ UTILS_NOINLINE
void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver,
FrameGraphResources::RenderPassInfo const& out, FMaterialInstance const* mi,
PostProcessVariant const variant) const noexcept {
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
FMaterial const* const ma = mi->getMaterial();
PipelineState const pipeline = getPipelineState(ma, variant);
@@ -645,7 +649,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
auto th = driver.createTextureView(in, level, 1);
mi->setParameter("depth", th, SamplerParams{
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
renderFullScreenQuad(out, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
@@ -1079,7 +1083,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
mi->setParameter("ssctRayCount",
float2{ options.ssct.rayCount, 1.0f / float(options.ssct.rayCount) });
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(ma);
@@ -1200,7 +1204,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
mi->setParameter("sampleCount", kGaussianCount);
mi->setParameter("farPlaneOverEdgeDistance", -zf / config.bilateralThreshold);
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(ma);
@@ -1895,7 +1899,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres?
mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h });
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
renderFullScreenQuad(out, pipeline, driver);
@@ -2425,7 +2429,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
mi->setParameter("source", hwOutView, SamplerParams{
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST});
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
renderFullScreenQuad(hwDstRT, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
@@ -2533,7 +2537,7 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
FMaterialInstance* const mi =
configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
vignetteOptions, width, height);
mi->commit(driver);
mi->commit(driver, getUboManager());
}
void PostProcessManager::colorGradingSubpass(DriverApi& driver,
@@ -2566,7 +2570,7 @@ void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomRe
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.customResolve = fixedIndex;
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
mi->commit(driver);
mi->commit(driver, getUboManager());
material.getMaterial(mEngine);
}
@@ -2619,7 +2623,7 @@ void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver) noexcep
auto ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.clearDepth = fixedIndex;
mi->commit(driver);
mi->commit(driver, getUboManager());
material.getMaterial(mEngine);
}
@@ -3111,7 +3115,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
mat4f{ historyProjection * inverse(current.projection) } *
normalizedToClip);
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
if (colorGradingConfig.asSubpass) {
@@ -3196,7 +3200,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
mi->setParameter("resolution", float4{
outputDesc.width, outputDesc.height,
1.0f / outputDesc.width, 1.0f / outputDesc.height });
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(material.getMaterial(mEngine), variant);
@@ -3287,7 +3291,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height
});
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
auto out = resources.getRenderPassInfo();
@@ -3374,7 +3378,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleSGSR1(FrameGraph& fg,
float(inputDesc.height)
});
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
auto const out = resources.getRenderPassInfo();
@@ -3469,7 +3473,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
mi->setParameter("resolution",
float4{ outputDesc.width, outputDesc.height,
1.0f / outputDesc.width, 1.0f / outputDesc.height });
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
}
@@ -3496,7 +3500,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height
});
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
}
@@ -3582,7 +3586,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
if (layer) {
mi->setParameter("layerIndex", layer);
}
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
auto pipeline = getPipelineState(ma);
@@ -3840,7 +3844,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
});
mi->setParameter("layer", uint32_t(layer));
mi->setParameter("uvscale", 1.0f / float(dim));
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
renderFullScreenQuadWithScissor(out, pipeline, scissor, driver);
@@ -3942,7 +3946,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height
});
mi->commit(driver);
mi->commit(driver, getUboManager());
mi->use(driver);
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.
for (uint32_t i = 0; i < inputTextureDesc.depth; ++i) {
mi->setParameter("layerIndex", i);
mi->commit(driver);
mi->commit(driver, getUboManager());
renderFullScreenQuad(out, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
// From the second draw, don't clear the targetbuffer.
+3
View File
@@ -65,6 +65,7 @@ class FMaterialInstance;
class FrameGraph;
class RenderPass;
class RenderPassBuilder;
class UboManager;
struct CameraInfo;
class PostProcessManager {
@@ -422,6 +423,8 @@ private:
return getMaterialInstance(ma);
}
UboManager* getUboManager() const noexcept;
backend::RenderPrimitiveHandle mFullScreenQuadRph;
backend::VertexBufferInfoHandle mFullScreenQuadVbih;
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;
assert_invariant(index < mCustomCommands.size());
pCustomCommands[index]();
currentPipeline = {};
currentPrimitiveHandle ={};
continue;
}
@@ -1069,26 +1071,34 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] =
ma->getDescriptorSetLayout(info.materialVariant).getHandle();
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
// It is possible to get a post-process material here (even though it's
// not technically a public API yet, it is used by the IBLPrefilterLibrary.
// Ideally we would have a more formal compute API). In this case, we need
// to set the post-process descriptor-set.
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
} else {
// If we have a ColorPassDescriptorSet we use it to bind the per-view
// descriptor-set (ideally only if it changed). If we don't, it means
// the descriptor-set is already bound and the layout we got from the
// material above should match. This is the case for situations where we
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and
// structure passes.
if (mColorPassDescriptorSet) {
// If we have a ColorPassDescriptorSet we use it to bind the per-view
// descriptor-set (ideally only if it changed).
// If we don't, it means the descriptor-set is already bound and the layout we
// got from the material above should match. This is the case for situations
// where we have a known per-view descriptor-set layout,
// e.g.: postfx, shadow-maps, ssr and structure passes.
if (mColorPassDescriptorSet) {
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
// It is possible to get a post-process material here (even though it's
// not technically a public API yet, it is used by the IBLPrefilterLibrary.
// Ideally we would have a more formal compute API). In this case, we need
// to set the post-process descriptor-set.
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
} else {
// 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
// material.
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.
+4
View File
@@ -178,6 +178,10 @@ View::DynamicResolutionOptions View::getDynamicResolutionOptions() const noexcep
return downcast(this)->getDynamicResolutionOptions();
}
math::float2 View::getLastDynamicResolutionScale() const noexcept {
return downcast(this)->getLastDynamicResolutionScale();
}
void View::setRenderQuality(const RenderQuality& renderQuality) noexcept {
downcast(this)->setRenderQuality(renderQuality);
}
+17 -2
View File
@@ -16,6 +16,7 @@
#include "details/BufferAllocator.h"
#include <private/utils/Tracing.h>
#include <utils/Panic.h>
#include <utils/debug.h>
@@ -142,7 +143,11 @@ void BufferAllocator::retire(AllocationId id) {
InternalSlotNode* targetNode = getNodeById(id);
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) {
@@ -157,10 +162,19 @@ void BufferAllocator::releaseGpu(AllocationId id) {
assert_invariant(targetNode != nullptr);
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() {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
if (!mHasPendingFrees) {
return;
}
auto curr = mSlotPool.begin();
while (curr != mSlotPool.end()) {
if (!curr->slot.isFree()) {
@@ -199,6 +213,7 @@ void BufferAllocator::releaseFreeSlots() {
curr = next;
}
mHasPendingFrees = false;
}
BufferAllocator::allocation_size_t BufferAllocator::getTotalSize() const noexcept {
+2 -1
View File
@@ -113,8 +113,9 @@ private:
[[nodiscard]] InternalSlotNode* getNodeById(AllocationId id) const noexcept;
bool mHasPendingFrees = false;
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::multimap</*slot size*/allocation_size_t, InternalSlotNode*> mFreeList;
std::unordered_map</*slot offset*/allocation_size_t, InternalSlotNode*> mOffsetMap;
+31 -14
View File
@@ -24,27 +24,35 @@
#include <filament/Camera.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Panic.h>
#include <math/scalar.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <limits>
using namespace filament::math;
using namespace utils;
namespace filament {
static constexpr const float MIN_APERTURE = 0.5f;
static constexpr const float MAX_APERTURE = 64.0f;
static constexpr const float MIN_SHUTTER_SPEED = 1.0f / 25000.0f;
static constexpr const float MAX_SHUTTER_SPEED = 60.0f;
static constexpr const float MIN_SENSITIVITY = 10.0f;
static constexpr const float MAX_SENSITIVITY = 204800.0f;
static constexpr float MIN_APERTURE = 0.5f;
static constexpr float MAX_APERTURE = 64.0f;
static constexpr float MIN_SHUTTER_SPEED = 1.0f / 25000.0f;
static constexpr float MAX_SHUTTER_SPEED = 60.0f;
static constexpr float MIN_SENSITIVITY = 10.0f;
static constexpr float MAX_SENSITIVITY = 204800.0f;
FCamera::FCamera(FEngine& engine, Entity const e)
: mEngine(engine),
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,
@@ -84,12 +92,16 @@ mat4 FCamera::projection(double const focalLengthInMillimeters,
* All methods for setting the projection funnel through here
*/
void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p,
mat4 const& c, double const near, double const far) noexcept {
void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& projection,
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) {
eyeProjection = p;
eyeProjection = projection;
}
mProjectionForCulling = c;
mProjectionForCulling = projectionForCulling;
mNear = near;
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,
mat4 const& projectionForCulling, double const near, double const far) {
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)
<< "All eye projections must be supplied together, count must be >= "
"config.stereoscopicEyeCount ("
<< config.stereoscopicEyeCount << ")";
for (int i = 0; i < config.stereoscopicEyeCount; i++) {
mEyeProjection[i] = projection[i];
}
@@ -290,10 +307,10 @@ CameraInfo::CameraInfo(FCamera const& camera,
model = mat4f{ modelMatrix };
view = mat4f{ inverse(modelMatrix) };
worldTransform = inWorldTransform;
zn = (float)camera.getNear();
zf = (float)camera.getCullingFar();
zn = float(camera.getNear());
zf = float(camera.getCullingFar());
ev100 = Exposure::ev100(camera);
f = (float)camera.getFocalLength();
f = float(camera.getFocalLength());
A = f / camera.getAperture();
d = std::max(zn, camera.getFocusDistance());
}
+47 -6
View File
@@ -468,11 +468,26 @@ void FEngine::init() {
}
mDefaultMaterial = downcast(defaultMaterialBuilder.build(*this));
}
// 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.
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)) {
// 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));
constexpr float3 dummyPositions[1] = {};
@@ -653,6 +668,12 @@ void FEngine::shutdown() {
driver.destroyRenderTarget(std::move(mDefaultRenderTarget));
if (isUboBatchingEnabled()) {
mUboManager->terminate(driver);
delete mUboManager;
mUboManager = nullptr;
}
/*
* 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
// skipped if the UBO hasn't changed. Still we could have a lot of these.
DriverApi& driver = getDriverApi();
const bool useUboBatching = isUboBatchingEnabled();
if (useUboBatching) {
assert_invariant(mUboManager != nullptr);
mUboManager->beginFrame(driver);
}
UboManager* uboManager = mUboManager;
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
// parameters are typically not set at this point in time.
if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) {
item->commitStreamUniformAssociations(driver);
item->commit(driver);
item->commit(driver, uboManager);
}
});
}
if (useUboBatching) {
assert_invariant(mUboManager != nullptr);
getUboManager()->finishBeginFrame(getDriverApi());
}
mMaterials.forEach([](FMaterial* material) {
#if FILAMENT_ENABLE_MATDBG // NOLINT(*-include-cleaner)
material->checkProgramEdits();
@@ -721,6 +754,13 @@ void FEngine::gc() {
mCameraManager.gc(*this, em);
}
void FEngine::submitFrame() {
if (isUboBatchingEnabled()) {
DriverApi& driver = getDriverApi();
getUboManager()->endFrame(driver);
}
}
void FEngine::flush() {
// flush the command buffer
flushCommandBuffer(mCommandBufferQueue);
@@ -951,9 +991,10 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
return p;
}
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
const char* name) noexcept {
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name);
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name,
UboBatchingMode batchingMode) noexcept {
FMaterialInstance* p =
mHeapAllocator.make<FMaterialInstance>(*this, material, name, batchingMode);
if (UTILS_LIKELY(p)) {
auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
pos.first->second.insert(p);
+26 -5
View File
@@ -21,16 +21,17 @@
#include "Allocators.h"
#include "DFG.h"
#include "PostProcessManager.h"
#include "ResourceList.h"
#include "HwDescriptorSetLayoutFactory.h"
#include "HwVertexBufferInfoFactory.h"
#include "MaterialCache.h"
#include "PostProcessManager.h"
#include "ResourceList.h"
#include "UboManager.h"
#include "components/CameraManager.h"
#include "components/LightManager.h"
#include "components/TransformManager.h"
#include "components/RenderableManager.h"
#include "components/TransformManager.h"
#include "ds/DescriptorSetLayout.h"
@@ -263,6 +264,14 @@ public:
return mBackend;
}
bool isUboBatchingEnabled() const noexcept {
return mUboManager != nullptr;
}
UboManager* getUboManager() noexcept {
return mUboManager;
}
Platform* getPlatform() const noexcept {
return mPlatform;
}
@@ -336,11 +345,21 @@ public:
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,
const FMaterialInstance* other, const char* name) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material,
const char* name) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name,
UboBatchingMode batchingMode) noexcept;
FScene* createScene() noexcept;
FView* createView() noexcept;
@@ -446,6 +465,7 @@ public:
void prepare();
void gc();
void submitFrame();
using ShaderContent = utils::FixedCapacityVector<uint8_t>;
@@ -650,6 +670,7 @@ private:
uint32_t mFlushCounter = 0;
UboManager* mUboManager = nullptr;
RootArenaScope::Arena mPerRenderPassArena;
HeapAllocatorArena mHeapAllocator;
+13 -11
View File
@@ -73,6 +73,7 @@ namespace filament {
using namespace backend;
using namespace filaflat;
using namespace utils;
using UboBatchingMode = FEngine::UboBatchingMode;
struct Material::BuilderDetails {
const void* mPayload = nullptr;
@@ -217,16 +218,17 @@ void FMaterial::terminate(FEngine& engine) {
filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
if (Variant::isValidDepthVariant(variant)) {
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
return mEngine.getPerViewDescriptorSetLayoutDepthVariant();
}
if (Variant::isSSRVariant(variant)) {
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
return mEngine.getPerViewDescriptorSetLayoutSsrVariant();
if (mDefinition.materialDomain == MaterialDomain::SURFACE) {
// `variant` is only sensical for MaterialDomain::SURFACE
if (Variant::isValidDepthVariant(variant)) {
return mEngine.getPerViewDescriptorSetLayoutDepthVariant();
}
if (Variant::isSSRVariant(variant)) {
return mEngine.getPerViewDescriptorSetLayoutSsrVariant();
}
}
// mDefinition.perViewDescriptorSetLayout{Vsm} is already resolved for MaterialDomain
if (useVsmDescriptorSetLayout) {
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
return mDefinition.perViewDescriptorSetLayoutVsm;
}
return mDefinition.perViewDescriptorSetLayout;
@@ -280,14 +282,14 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
return FMaterialInstance::duplicate(mDefaultMaterialInstance, name);
} else {
// 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 {
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
mDefaultMaterialInstance = mEngine.createMaterialInstance(this,
mDefinition.name.c_str());
mDefaultMaterialInstance =
mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT);
mDefaultMaterialInstance->setDefaultInstance(true);
}
return mDefaultMaterialInstance;
+1 -1
View File
@@ -108,7 +108,7 @@ public:
backend::CallbackHandler* handler,
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;
bool hasParameter(const char* name) const noexcept;
+48 -16
View File
@@ -58,15 +58,19 @@ using namespace utils;
namespace filament {
using namespace backend;
using UboBatchingMode = FEngine::UboBatchingMode;
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name) noexcept
: FMaterialInstance(engine, material, name,
engine.features.material.enable_material_instance_uniform_batching) {
bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) {
if (batchingMode != UboBatchingMode::DEFAULT) {
return batchingMode == UboBatchingMode::UBO_BATCHING;
}
return engine.isUboBatchingEnabled() && domain == MaterialDomain::SURFACE;
}
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name, bool useUboBatching) noexcept : mMaterial(material),
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
UboBatchingMode batchingMode) noexcept
: mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mCulling(CullingMode::BACK),
mShadowCulling(CullingMode::BACK),
@@ -76,10 +80,12 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
mHasScissor(false),
mIsDoubleSided(false),
mIsDefaultInstance(false),
mUseUboBatching(useUboBatching),
mUseUboBatching(
shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())),
mTransparencyMode(TransparencyMode::DEFAULT),
mName(name ? CString(name) : material->getName()) {
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
<< "UBO batching is not enabled.";
FEngine::DriverApi& driver = engine.getDriverApi();
// 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) {
mUboData = BufferAllocator::UNALLOCATED;
engine.getUboManager()->manageMaterialInstance(this);
} else {
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
BufferUsage::STATIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -153,7 +160,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mUseUboBatching(other->mUseUboBatching),
mScissorRect(other->mScissorRect),
mName(name ? CString(name) : other->mName) {
assert_invariant(!mUseUboBatching || engine.isUboBatchingEnabled());
FEngine::DriverApi& driver = engine.getDriverApi();
FMaterial const* const material = other->getMaterial();
@@ -161,6 +168,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
if (mUseUboBatching) {
mUboData = BufferAllocator::UNALLOCATED;
engine.getUboManager()->manageMaterialInstance(this);
} else {
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
BufferUsage::DYNAMIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -193,8 +201,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
}
}
FMaterialInstance* FMaterialInstance::duplicate(
FMaterialInstance const* other, const char* name) noexcept {
FMaterialInstance* FMaterialInstance::duplicate(FMaterialInstance const* other,
const char* name) noexcept {
FMaterial const* const material = other->getMaterial();
FEngine& engine = material->getEngine();
return engine.createMaterialInstance(material, other, name);
@@ -205,6 +213,10 @@ FMaterialInstance::~FMaterialInstance() noexcept = default;
void FMaterialInstance::terminate(FEngine& engine) {
FEngine::DriverApi& driver = engine.getDriverApi();
mDescriptorSet.terminate(driver);
if (mUseUboBatching) {
engine.getUboManager()->unmanageMaterialInstance(this);
}
auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
if (ubHandle){
driver.destroyBufferObject(*ubHandle);
@@ -238,15 +250,21 @@ void FMaterialInstance::commitStreamUniformAssociations(FEngine::DriverApi& driv
void FMaterialInstance::commit(FEngine& engine) const {
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) {
mUniforms.clean();
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 {
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
fixMissingSamplers();
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
// Commit descriptors if needed (e.g. when textures are updated,or the first time)
mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver);
}
@@ -413,6 +435,13 @@ const char* FMaterialInstance::getName() const noexcept {
// ------------------------------------------------------------------------------------------------
void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
if (!mDescriptorSet.getHandle()) {
return;
}
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) {
std::call_once(mMissingSamplersFlag, [this] {
@@ -439,7 +468,8 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
return;
}
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL,
{ { mUboOffset }, driver });
}
void FMaterialInstance::assignUboAllocation(
@@ -449,8 +479,10 @@ void FMaterialInstance::assignUboAllocation(
assert_invariant(mUseUboBatching);
mUboData = id;
mUboOffset = offset;
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());
}
}
+5 -7
View File
@@ -56,11 +56,8 @@ class FTexture;
class FMaterialInstance : public MaterialInstance {
public:
FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name) 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, FMaterial const* material, const char* name,
FEngine::UboBatchingMode batchingMode) noexcept;
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
FMaterialInstance(const FMaterialInstance& rhs) = delete;
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
@@ -75,7 +72,7 @@ public:
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;
@@ -287,6 +284,7 @@ private:
};
std::variant<BufferAllocator::AllocationId, backend::Handle<backend::HwBufferObject>> mUboData;
BufferAllocator::allocation_size_t mUboOffset = 0;
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms;
@@ -308,7 +306,7 @@ private:
bool mHasScissor : 1;
bool mIsDoubleSided : 1;
bool mIsDefaultInstance : 1;
bool mUseUboBatching : 1;
const bool mUseUboBatching : 1;
TransparencyMode mTransparencyMode : 2;
uint64_t mMaterialSortingKey = 0;
+4
View File
@@ -452,6 +452,8 @@ void FRenderer::endFrame() {
mFrameInfoManager.endFrame(driver);
mFrameSkipper.submitFrame(driver);
engine.submitFrame();
driver.endFrame(mFrameId);
// gives the backend a chance to execute periodic tasks
@@ -587,6 +589,8 @@ void FRenderer::renderStandaloneView(FView const* view) {
// happen with Renderer::beginFrame/endFrame.
renderInternal(view, true);
engine.submitFrame();
driver.endFrame(mFrameId);
// 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 <backend/DriverEnums.h>
#include <private/utils/Tracing.h>
#include <utils/Logger.h>
#include <vector>
namespace filament {
namespace {
@@ -33,124 +35,20 @@ using AllocationId = BufferAllocator::AllocationId;
using allocation_size_t = BufferAllocator::allocation_size_t;
} // anonymous namespace
UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInBytes,
allocation_size_t defaultTotalSizeInBytes)
: 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");
// ------------------------------------------------------------------------------------------------
// FenceManager
// ------------------------------------------------------------------------------------------------
void UboManager::FenceManager::track(DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds) {
if (allocationIds.empty()) {
return;
}
// 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));
});
}
mFenceAllocationList.emplace_back(driver.createFence(), std::move(allocationIds));
}
void UboManager::finishBeginFrame(DriverApi& driver) {
if (mMemoryMappedBufferHandle) {
driver.unmapBuffer(mMemoryMappedBufferHandle);
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) {
void UboManager::FenceManager::reclaimCompletedResources(DriverApi& driver,
std::function<void(AllocationId)> const& onReclaimed) {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
uint32_t signaledCount = 0;
bool seenSignaledFence = false;
@@ -166,10 +64,8 @@ void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
signaledCount++;
#ifndef NDEBUG
if (UTILS_UNLIKELY(status != FenceStatus::CONDITION_SATISFIED)) {
LOG(WARNING) << "A fence is either an error or hasn't signaled, but the new fence has "
"been "
"signaled. Will release the resource anyways."
<< io::endl;
LOG(WARNING) << "A fence is either in an error state or hasn't signaled, but a newer "
"fence has. Will release the resource anyway.";
}
#endif
continue;
@@ -188,10 +84,10 @@ void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
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 (const AllocationId& id: it->second) {
mAllocator.releaseGpu(id);
for (const AllocationId& id : it->second) {
onReclaimed(id);
}
// Destroy the fence handle as it's no longer needed.
driver.destroyFence(std::move(it->first));
@@ -200,106 +96,211 @@ void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
mFenceAllocationList.erase(mFenceAllocationList.begin(), firstToKeep);
}
UboManager::AllocationResult UboManager::updateMaterialInstanceAllocations(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances,
AllocationMode allocationMode) {
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;
}
});
void UboManager::FenceManager::reset(DriverApi& driver) {
for (auto& [fence, _] : mFenceAllocationList) {
if (fence) {
driver.destroyFence(std::move(fence));
}
}
return result;
mFenceAllocationList.clear();
}
UboManager::AllocationResult UboManager::tryAllocateMaterialInstanceSlot(FMaterialInstance* mi,
BufferAllocator& allocator, const Handle<HwBufferObject>& ubHandle,
AllocationMode allocationMode) {
if (!mi->isUsingUboBatching()) {
return SUCCESS;
// ------------------------------------------------------------------------------------------------
// UboManager
// ------------------------------------------------------------------------------------------------
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();
auto allocateAndAssign = [&](AllocationId originalId) -> AllocationResult {
auto [newId, newOffset] = allocator.allocate(mi->getUniformBuffer().getSize());
// Special handling for instances that were previously UNALLOCATED:
// If the new allocation also fails, keep it UNALLOCATED to signal initial failure.
if (originalId == BufferAllocator::UNALLOCATED &&
newId == BufferAllocator::REALLOCATION_REQUIRED) {
newId = BufferAllocator::UNALLOCATED;
// Calculate the required size and grow the Ubo.
const allocation_size_t requiredSize = calculateRequiredSize();
reallocate(driver, requiredSize);
// Allocate slots for each MI on the new Ubo.
allocateAllInstances();
// 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
// process knows.
mi->assignUboAllocation(ubHandle, newId, newOffset);
mAllocator.acquireGpu(id);
allocationIds.insert(id);
}
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)) {
return REALLOCATION_REQUIRED;
reallocationNeeded = true;
}
return SUCCESS;
};
}
mPendingInstances.clear();
if (!BufferAllocator::isValid(id) || allocationMode == ALWAYS) {
// The material instance is first time being allocated (if !isValid)
// or is being forcibly re-allocated (if ALWAYS).
return allocateAndAssign(id);
// Pass 2: Allocate slots for existing material instances that need to be orphaned.
for (auto* mi: mManagedInstances) {
if (!BufferAllocator::isValid(mi->getAllocationId())) {
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)) {
// 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 reallocationNeeded ? REALLOCATION_REQUIRED : SUCCESS;
}
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) {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
if (mUbHandle) {
driver.destroyBufferObject(mUbHandle);
}
for (auto& [fence, allocations]: mFenceAllocationList) {
driver.destroyFence(std::move(fence));
}
mFenceAllocationList.clear();
mFenceManager.reset(driver);
mAllocator.reset(requiredSize);
mUboSize = requiredSize;
mUbHandle = driver.createBufferObject(requiredSize, BufferObjectBinding::UNIFORM,
BufferUsage::DYNAMIC | BufferUsage::SHARED_WRITE_BIT);
}
allocation_size_t UboManager::calculateRequiredSize(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances) {
BufferAllocator& allocator = mAllocator;
allocation_size_t UboManager::calculateRequiredSize() {
allocation_size_t newBufferSize = 0;
for (const auto& materialInstance: materialInstances) {
materialInstance.second.forEach([&newBufferSize, &allocator](const FMaterialInstance* mi) {
if (!mi->isUsingUboBatching()) {
return;
}
const AllocationId allocationId = mi->getAllocationId();
if (allocationId == BufferAllocator::REALLOCATION_REQUIRED) {
// 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());
}
});
for (const auto* mi: mManagedInstances) {
const AllocationId allocationId = mi->getAllocationId();
if (allocationId == BufferAllocator::REALLOCATION_REQUIRED) {
// 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());
} else {
newBufferSize += mAllocator.alignUp(mi->getUniformBuffer().getSize());
}
}
return allocator.alignUp(newBufferSize * BUFFER_SIZE_GROWTH_MULTIPLIER);
return mAllocator.alignUp(newBufferSize * BUFFER_SIZE_GROWTH_MULTIPLIER);
}
} // namespace filament
+72 -34
View File
@@ -17,7 +17,6 @@
#ifndef TNT_FILAMENT_DETAILS_UBOMANAGER_H
#define TNT_FILAMENT_DETAILS_UBOMANAGER_H
#include "ResourceList.h"
#include "backend/DriverApiForward.h"
#include "details/BufferAllocator.h"
@@ -25,7 +24,11 @@
#include <backend/Handle.h>
#include <private/backend/DriverApi.h>
#include <functional>
#include <unordered_set>
#include <vector>
class UboManagerTest;
namespace filament {
@@ -40,6 +43,47 @@ class FMaterialInstance;
// undefined behavior.
class UboManager {
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,
BufferAllocator::allocation_size_t defaultSlotSizeInBytes,
BufferAllocator::allocation_size_t defaultTotalSizeInBytes);
@@ -53,9 +97,8 @@ public:
// instances with modified uniforms).
// 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.
void beginFrame(backend::DriverApi& driver,
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances);
// Note that it must happen before committing all MIs.
void beginFrame(backend::DriverApi& driver);
// Unmap the buffer here
void finishBeginFrame(backend::DriverApi& driver);
@@ -63,62 +106,57 @@ public:
// 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
// After the corresponding frame has been done.
void endFrame(backend::DriverApi& driver,
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances);
void endFrame(backend::DriverApi& driver);
void terminate(backend::DriverApi& driver);
void updateSlot(backend::DriverApi& driver, BufferAllocator::AllocationId id,
backend::BufferDescriptor bufferDescriptor) const;
// Call this when a material instance is no longer holding a slot. e.g. it is destroyed.
void retireSlot(BufferAllocator::AllocationId id);
// Call this to register a new material instance to UboManager.
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
// reallocated to a bigger size at the next frame.
[[nodiscard]] BufferAllocator::allocation_size_t getTotalSize() const noexcept;
// For testing
[[nodiscard]] backend::MemoryMappedBufferHandle getMemoryMappedBufferHandle() const noexcept {
return mMemoryMappedBufferHandle;
}
private:
friend class ::UboManagerTest;
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 {
SUCCESS,
REALLOCATION_REQUIRED
};
enum AllocationMode {
ON_DEMAND,
ALWAYS
};
// Query the offset by the allocation id.
[[nodiscard]] BufferAllocator::allocation_size_t getAllocationOffset(
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);
BufferAllocator::allocation_size_t calculateRequiredSize(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances);
BufferAllocator::allocation_size_t calculateRequiredSize();
backend::Handle<backend::HwBufferObject> mUbHandle;
backend::MemoryMappedBufferHandle mMemoryMappedBufferHandle;
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.
using FenceAllocationList = std::vector<std::pair<backend::Handle<backend::HwFence>,
std::unordered_set<BufferAllocator::AllocationId>>>;
FenceAllocationList mFenceAllocationList;
FenceManager mFenceManager;
BufferAllocator mAllocator;
};
+33 -41
View File
@@ -285,53 +285,45 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const Builder& builder)
// calculate buffer sizes
size_t bufferSizes[MAX_VERTEX_BUFFER_COUNT] = {};
#pragma nounroll
for (size_t i = 0, n = mAttributes.size(); i < n; ++i) {
if (mDeclaredAttributes[i]) {
const uint32_t offset = mAttributes[i].offset;
const uint8_t stride = mAttributes[i].stride;
const uint8_t slot = mAttributes[i].buffer;
const size_t end = offset + mVertexCount * stride;
if (slot != Attribute::BUFFER_UNUSED) {
assert_invariant(slot < MAX_VERTEX_BUFFER_COUNT);
bufferSizes[slot] = std::max(bufferSizes[slot], end);
}
}
}
auto shouldCreateBuffer = [this](size_t attributeIndex) {
const uint8_t slot = mAttributes[attributeIndex].buffer;
return mDeclaredAttributes[attributeIndex] && slot != Attribute::BUFFER_UNUSED &&
!mBufferObjects[slot];
};
auto updateBufferSize = [&bufferSizes, this](size_t attributeIndex) {
const uint32_t offset = mAttributes[attributeIndex].offset;
const uint8_t stride = mAttributes[attributeIndex].stride;
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 buffer objects are not enabled at the API level, then we create them internally.
#pragma nounroll
for (size_t index = 0; index < MAX_VERTEX_BUFFER_COUNT; ++index) {
size_t const i = mAttributes[index].buffer;
if (i != Attribute::BUFFER_UNUSED) {
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;
}
for (size_t i = 0, n = mAttributes.size(); i < n; ++i) {
if (shouldCreateBuffer(i)) {
updateBufferSize(i);
}
}
} else {
// in advanced skinning mode, we manage the BONE_INDICES and BONE_WEIGHTS arrays ourselves,
// so we have to set the corresponding buffer objects.
if (mAdvancedSkinningEnabled) {
for (auto const index : { BONE_INDICES, BONE_WEIGHTS }) {
size_t const i = mAttributes[index].buffer;
assert_invariant(i != Attribute::BUFFER_UNUSED);
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 if (mAdvancedSkinningEnabled) {
// For advanced skinning mode, only relevant buffers (BONE_INDICES & BONE_WEIGHTS) are
// created. We manually populated the relevant attributes for those buffers above.
updateBufferSize(BONE_INDICES);
updateBufferSize(BONE_WEIGHTS);
}
// create buffers
for (size_t i = 0; i < MAX_VERTEX_BUFFER_COUNT; ++i) {
if (bufferSizes[i] == 0 || mBufferObjects[i]) {
continue;
}
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;
}
math::float2 getLastDynamicResolutionScale() const noexcept {
return mScale;
}
void setRenderQuality(RenderQuality const& renderQuality) noexcept {
mRenderQuality = renderQuality;
}
+1 -1
View File
@@ -39,7 +39,7 @@ void PostProcessDescriptorSet::init(FEngine& engine) noexcept {
// create the descriptor-set layout
mDescriptorSetLayout = filament::DescriptorSetLayout{
engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), descriptor_sets::getPostProcessLayout() };
engine.getDriverApi(), descriptor_sets::getDepthVariantLayout() };
// create the descriptor-set from the layout
mDescriptorSet = DescriptorSet{ "PostProcessDescriptorSet", mDescriptorSetLayout };
+6 -1
View File
@@ -44,18 +44,23 @@ list(APPEND RESGEN_SOURCE ${DUMMY_SRC})
# away in Release builds
if (TNT_DEV)
add_executable(test_${TARGET}
MockDriver.h
filament_AtlasAllocator_test.cpp
test_BufferAllocator.cpp
test_BufferAllocatorStress.cpp
test_CircularQueue.cpp
test_FenceManager.cpp
test_UboManager.cpp
filament_test_exposure.cpp
filament_rendering_test.cpp
filament_bimap_test.cpp
filament_framegraph_test.cpp
filament_test.cpp)
filament_test.cpp
${RESGEN_SOURCE})
target_link_libraries(test_${TARGET} PRIVATE filament gtest)
target_compile_options(test_${TARGET} PRIVATE ${COMPILER_FLAGS})
target_include_directories(test_${TARGET} PRIVATE ${RESOURCE_DIR})
set_target_properties(test_${TARGET} PROPERTIES FOLDER Tests)
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 {
backend::DescriptorSetLayout const& getPostProcessLayout() noexcept;
backend::DescriptorSetLayout const& getDepthVariantLayout() noexcept;
backend::DescriptorSetLayout const& getSsrVariantLayout() noexcept;
backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept;
backend::DescriptorSetLayout getPerViewDescriptorSetLayout(
+3 -16
View File
@@ -40,12 +40,7 @@ namespace filament::descriptor_sets {
using namespace backend;
// used for post-processing passes
static constexpr std::initializer_list<DescriptorSetLayoutBinding> postProcessDescriptorSetLayoutList = {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
};
// used to generate shadow-maps
// used to generate shadow-maps, structure and postfx passes
static constexpr std::initializer_list<DescriptorSetLayoutBinding> depthVariantDescriptorSetLayoutList = {
{ 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 }
};
// used for post-processing passes
static DescriptorSetLayout const postProcessDescriptorSetLayout{ utils::StaticString("postProcess"),
postProcessDescriptorSetLayoutList };
// used to generate shadow-maps
static DescriptorSetLayout const depthVariantDescriptorSetLayout{
utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList
@@ -163,10 +154,6 @@ static DescriptorSetLayout perRenderableDescriptorSetLayout = {
utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList
};
DescriptorSetLayout const& getPostProcessLayout() noexcept {
return postProcessDescriptorSetLayout;
}
DescriptorSetLayout const& getDepthVariantLayout() noexcept {
return depthVariantDescriptorSetLayout;
}
@@ -280,10 +267,10 @@ DescriptorSetLayout getPerViewDescriptorSetLayout(
return layout;
}
case MaterialDomain::POST_PROCESS:
return postProcessDescriptorSetLayout;
return depthVariantDescriptorSetLayout;
case MaterialDomain::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;
layout.bindings.reserve(1 + samplers.size());
layout.bindings.push_back(DescriptorSetLayoutBinding { DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::NONE, 0 });
layout.bindings.push_back(DescriptorSetLayoutBinding{ DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::DYNAMIC_OFFSET, 0 });
for (auto const& sampler: samplers) {
DescriptorSetLayoutBinding layoutBinding{
@@ -225,7 +225,7 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER));
f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT));
f.writeUint8(0);
f.writeUint8(uint8_t(DescriptorFlags::NONE));
f.writeUint8(uint8_t(DescriptorFlags::DYNAMIC_OFFSET));
f.writeUint16(0);
// all the material's sampler descriptors
+7
View File
@@ -13,6 +13,11 @@ set(PUBLIC_HDRS
)
set(SRCS
src/DirIncluder.h
src/DirIncluder.cpp
src/IncludeCallback.h
src/Includes.h
src/Includes.cpp
src/JsonishLexeme.h
src/JsonishLexer.h
src/JsonishParser.h
@@ -53,6 +58,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/ DESTINATION include)
project(test_matp)
set(TARGET test_matp)
set(SRCS
tests/test_includes.cpp
tests/test_includer.cpp
tests/test_matp.cpp
tests/TestMaterialParser.h
tests/test_compute_material.cpp
@@ -155,6 +155,9 @@ public:
return mWorkarounds;
}
bool getInsertLineDirectives() const noexcept { return mInsertLineDirectives; }
bool getInsertLineDirectiveChecks() const noexcept { return mInsertLineDirectiveChecks; }
protected:
bool mDebug = false;
bool mIsValid = true;
@@ -174,6 +177,8 @@ protected:
filament::UserVariantFilterMask mVariantFilter = 0;
Workarounds mWorkarounds = Workarounds::ALL;
bool mIncludeEssl1 = true;
bool mInsertLineDirectives = true;
bool mInsertLineDirectiveChecks = true;
};
} // namespace matp
@@ -20,9 +20,11 @@
#include <unordered_map>
#include <utils/Status.h>
#include "Config.h"
#include <utils/Path.h>
#include <utils/Status.h>
namespace filamat {
class MaterialBuilder;
}
@@ -36,7 +38,13 @@ class MaterialLexeme;
class MaterialParser {
public:
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.
// Call MaterialBuilder::init before passing in the builder; call MaterialBuilder::build to
// create filamat::Package after.
@@ -20,7 +20,7 @@
#include <fstream>
namespace matc {
namespace matp {
bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& result) {
auto getHeaderPath = [&result, &includedBy, this]() {
@@ -66,5 +66,5 @@ bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& re
return true;
}
} // namespace matc
} // namespace matp
@@ -21,7 +21,7 @@
#include <utils/Path.h>
namespace matc {
namespace matp {
// Functor callback handler used to resolve includes relative to a root include directory.
class DirIncluder {
@@ -38,6 +38,6 @@ private:
};
} // namespace matc
} // namespace matp
#endif
@@ -18,10 +18,11 @@
#define TNT_INCLUDER_H
#include <utils/CString.h>
#include <utils/Status.h>
#include <functional>
namespace matc {
namespace matp {
struct IncludeResult {
// The include name of the root file, as if it were being included.
@@ -30,7 +31,7 @@ struct IncludeResult {
const utils::CString includeName;
// 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
// directives.
@@ -58,12 +59,12 @@ struct IncludeResult {
* @param result is the IncludeResult that the callback should fill out.
* @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(
const utils::CString& includedBy,
IncludeResult& result)>;
} // namespace matc
} // namespace matp
#endif
@@ -19,10 +19,11 @@
#include <utils/Log.h>
#include <utils/compiler.h>
#include <utils/sstream.h>
#include <utils/Status.h>
#include <string>
namespace matc {
namespace matp {
static bool isWhitespace(char c) {
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) {
if (depth > 30) {
// 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 false;
return utils::Status::invalidArgument("Include depth > 30. Include cycle?");
}
utils::CString& text = root.text;
@@ -88,7 +88,7 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
std::vector<FoundInclude> includes = parseForIncludes(text);
if (includes.empty()) {
// No more to resolve.
return true;
return utils::Status::ok();
}
const size_t lineNumberOffset = includes[0].line;
@@ -196,28 +196,30 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
const auto include = includes[0];
// Ask the includer to resolve this include.
if (!callback) {
return false;
return utils::Status::invalidArgument("No callback is provided");
}
IncludeResult resolved {
IncludeResult includeResult{
.includeName = include.name
};
if (!callback(root.name, resolved)) {
utils::slog.e << "The included file \"" << include.name.c_str()
if (!callback(root.name, includeResult)) {
utils::io::sstream errorMessage;
errorMessage << "The included file \"" << include.name.c_str()
<< "\" could not be found." << utils::io::endl;
return false;
return utils::Status::invalidArgument(errorMessage.c_str());
}
// Recursively resolve all of its includes.
if (!resolveIncludes(resolved, callback, options, depth + 1)) {
return false;
if (utils::Status resolveStatus =
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);
}
return true;
return utils::Status::ok();
}
std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
@@ -308,4 +310,4 @@ std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
return results;
}
} // namespace matc
} // namespace matp
@@ -20,10 +20,11 @@
#include "IncludeCallback.h"
#include <utils/CString.h>
#include <utils/Status.h>
#include <vector>
namespace matc {
namespace matp {
struct ResolveOptions {
// If true, insert #line directives before / after each include.
@@ -39,9 +40,10 @@ struct ResolveOptions {
};
// Recursively handle all the includes inside of root.
// Returns true if all includes were handled successfully, false otherwise.
// callback may be null, in which case any #include directives found will result in a failure.
bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
// Returns utils::Status::StatusCode::OK if all includes were handled successfully, otherwise an
// error status with a failure message.
// 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);
struct FoundInclude {
@@ -53,6 +55,6 @@ struct FoundInclude {
std::vector<FoundInclude> parseForIncludes(const utils::CString& source);
} // namespace matc
} // namespace matp
#endif
+31
View File
@@ -16,6 +16,8 @@
#include <filament-matp/MaterialParser.h>
#include "DirIncluder.h"
#include "Includes.h"
#include "JsonishLexer.h"
#include "JsonishParser.h"
#include "MaterialLexeme.h"
@@ -393,6 +395,35 @@ utils::Status MaterialParser::processMaterialParameters(filamat::MaterialBuilder
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(
const Config& config, ssize_t& size, std::unique_ptr<const char[]>& buffer) {
const auto& templateMap = config.getTemplateMap();
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include <matc/IncludeCallback.h>
#include "IncludeCallback.h"
#include <utils/CString.h>
@@ -37,7 +37,7 @@ public:
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 found = mIncludeMap.find(key);
@@ -16,17 +16,17 @@
#include <gtest/gtest.h>
#include <matc/DirIncluder.h>
#include "DirIncluder.h"
using namespace utils;
using namespace matc;
using namespace matp;
const utils::Path root = utils::Path(__FILE__).getParent();
// TODO: these tests are disabled as they fail on CI, which needs investigation.
TEST(DirIncluder, DISABLED_IncludeNonexistent) {
matc::DirIncluder includer;
DirIncluder includer;
{
IncludeResult i {
.includeName = CString("nonexistent.h")
@@ -45,7 +45,7 @@ TEST(DirIncluder, DISABLED_IncludeNonexistent) {
}
TEST(DirIncluder, DISABLED_IncludeFile) {
matc::DirIncluder includer;
DirIncluder includer;
includer.setIncludeDirectory(root);
IncludeResult result {
@@ -63,7 +63,7 @@ TEST(DirIncluder, DISABLED_IncludeFile) {
}
TEST(DirIncluder, DISABLED_IncludeFileFromIncluder) {
matc::DirIncluder includer;
DirIncluder includer;
includer.setIncludeDirectory(root);
utils::CString includerFile((root + "Dir/Baz.h").c_str());
@@ -77,8 +77,3 @@ TEST(DirIncluder, DISABLED_IncludeFileFromIncluder) {
EXPECT_STREQ((root + "Dir/Bar.h").c_str(), result.name.c_str());
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
@@ -16,9 +16,9 @@
#include <gtest/gtest.h>
#include "Includes.h"
#include "MockIncluder.h"
#include <matc/Includes.h>
#include <filamat/MaterialBuilder.h>
#include <utils/CString.h>
@@ -27,7 +27,7 @@
#include <memory>
using namespace utils;
using namespace matc;
using namespace matp;
// -------------------------------------------------------------------------------------------------
@@ -211,8 +211,8 @@ TEST(IncludeResolver, NoIncludes) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ("no includes", source.text.c_str());
}
@@ -224,8 +224,8 @@ TEST(IncludeResolver, SingleInclude) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ("include", source.text.c_str());
}
@@ -245,8 +245,8 @@ TEST(IncludeResolver, MultipleIncludes) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ(utils::CString(R"(
1
2
@@ -270,8 +270,8 @@ TEST(IncludeResolver, IncludeWithinInclude) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ(utils::CString(R"(
1
3
@@ -293,8 +293,8 @@ TEST(IncludeResolver, Includers) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ(utils::CString(R"(
3
)").c_str(), source.text.c_str());
@@ -313,8 +313,8 @@ TEST(IncludeResolver, IncludeFailure) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
EXPECT_FALSE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_FALSE(resolved.isOk());
}
{
utils::CString code(R"(
@@ -326,8 +326,8 @@ TEST(IncludeResolver, IncludeFailure) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
EXPECT_FALSE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_FALSE(resolved.isOk());
}
}
@@ -344,9 +344,9 @@ TEST(IncludeResolver, Cycle) {
IncludeResult source {
.text = code
};
bool result = resolveIncludes(source, includer, {});
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
// Include cycles are disallowed. We should still terminate in finite time and report false.
EXPECT_FALSE(result);
EXPECT_FALSE(resolved.isOk());
}
// Helper function that lets us write comparison cases with line breaks.
@@ -395,8 +395,8 @@ TEST(IncludeResolver, SingleIncludeLineDirective) {
.includeName = utils::CString("root.h"),
.text = code
};
bool result = resolveIncludes(source, includer, options);
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, options);
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ_TRIMMARGIN(R"(
| #line 1 "root.h"
| #line 1 "test.h"
@@ -421,8 +421,8 @@ TEST(IncludeResolver, MultipleIncludesLineDirective) {
.includeName = utils::CString("root.h"),
.text = code,
};
bool result = resolveIncludes(source, includer, options);
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, options);
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ_TRIMMARGIN(R"(
| #line 1 "root.h"
@@ -453,8 +453,8 @@ TEST(IncludeResolver, MultipleIncludesSameLineLineDirective) {
.includeName = utils::CString("root.h"),
.text = code
};
bool result = resolveIncludes(source, includer, options);
EXPECT_TRUE(result);
utils::Status resolved = resolveIncludesRecursively(source, includer, options);
EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ_TRIMMARGIN(R"(
| #line 1 "root.h"
@@ -518,7 +518,8 @@ TEST_F(MaterialBuilder, Include) {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
};
EXPECT_TRUE(resolveIncludes(source, std::move(includer), options));
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
EXPECT_TRUE(resolved.isOk());
// Set the material with the resolved material.
mBuilder.material(source.text.c_str());
@@ -545,8 +546,8 @@ TEST_F(MaterialBuilder, IncludeVertex) {
IncludeResult source {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
};
EXPECT_TRUE(resolveIncludes(source, std::move(includer), options));
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
EXPECT_TRUE(resolved.isOk());
// Set the materialVertex with the resolved material.
mBuilder.materialVertex(source.text.c_str());
@@ -572,7 +573,8 @@ TEST_F(MaterialBuilder, IncludeWithinFunction) {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
};
EXPECT_TRUE(resolveIncludes(source, std::move(includer), options));
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
EXPECT_TRUE(resolved.isOk());
// Set the material with the resolved material.
mBuilder.material(source.text.c_str());
@@ -592,7 +594,8 @@ TEST_F(MaterialBuilder, IncludeFailure) {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
};
EXPECT_FALSE(resolveIncludes(source, std::move(includer), options));
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
EXPECT_FALSE(resolved.isOk());
mBuilder.material(source.text.c_str());
+4 -4
View File
@@ -17,13 +17,13 @@
#include <gtest/gtest.h>
#include "TestMaterialParser.h"
#include <filamat/MaterialBuilder.h>
#include <filament-matp/MaterialParser.h>
#include "JsonishLexer.h"
#include "JsonishParser.h"
#include "MaterialLexer.h"
#include "MockIncluder.h"
#include <filamat/MaterialBuilder.h>
#include <filament-matp/MaterialParser.h>
class MaterialLexer: public ::testing::Test {
protected:
+8
View File
@@ -37,6 +37,7 @@ set(SRCS
src/IcoSphere.cpp
src/MeshAssimp.cpp
src/Sphere.cpp
src/VulkanPlatformHelper.cpp
)
set(LIBS
@@ -67,18 +68,25 @@ set(MATERIAL_SRCS
if (APPLE)
list(APPEND SRCS src/NativeWindowHelperCocoa.mm)
list(APPEND SRCS src/VulkanPlatformHelperApple.mm)
list(APPEND LIBS "-framework Cocoa -framework QuartzCore")
endif()
if (LINUX)
list(APPEND SRCS src/NativeWindowHelperLinux.cpp)
list(APPEND SRCS src/VulkanPlatformHelperLinux.cpp)
endif()
if (WIN32)
list(APPEND SRCS src/NativeWindowHelperWindows.cpp)
list(APPEND SRCS src/VulkanPlatformHelperWindows.cpp)
list(APPEND LIBS sdl2main)
endif()
if (ANDROID)
list(APPEND SRCS src/VulkanPlatformHelperAndroid.cpp)
endif()
# ==================================================================================================
# Compile resources
# ==================================================================================================

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