Compare commits

..
Author SHA1 Message Date
Run Yu 5f14ebba87 Use MapAsync and AllowProcessEvents for callback 2025-11-19 11:19:27 -05: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
95 changed files with 1787 additions and 583 deletions
+1 -1
View File
@@ -179,7 +179,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: ./.github/actions/linux-prereq - uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
run: ./build.sh -W debug test_filamat filament run: ./build.sh -W debug test_filamat filament gltf_viewer
- name: Run test - name: Run test
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl* run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
+4
View File
@@ -6,3 +6,7 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut ## Release notes for next branch cut
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.67.1' implementation 'com.google.android.filament:filament-android:1.67.0'
} }
``` ```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release: iOS projects can use CocoaPods to install the latest release:
```shell ```shell
pod 'Filament', '~> 1.67.1' pod 'Filament', '~> 1.67.0'
``` ```
## Documentation ## Documentation
@@ -147,6 +147,15 @@ Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*, jcla
view->setDynamicResolutionOptions(options); view->setDynamicResolutionOptions(options);
} }
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nGetLastDynamicResolutionScale(JNIEnv *env, jclass, jlong nativeView, jfloatArray out_) {
jfloat* out = env->GetFloatArrayElements(out_, nullptr);
View *view = (View *) nativeView;
math::float2 result = view->getLastDynamicResolutionScale();
std::copy_n(result.v, 2, out);
env->ReleaseFloatArrayElements(out_, out, 0);
}
extern "C" JNIEXPORT void JNICALL extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) { Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) {
View* view = (View*) nativeView; View* view = (View*) nativeView;
@@ -99,6 +99,15 @@ final class Asserts {
} }
} }
@NonNull @Size(min = 2)
static float[] assertFloat2(@Nullable float[] out) {
if (out == null) out = new float[2];
else if (out.length < 2) {
throw new ArrayIndexOutOfBoundsException("Array length must be at least 2");
}
return out;
}
@NonNull @Size(min = 4) @NonNull @Size(min = 4)
static float[] assertFloat4(@Nullable float[] out) { static float[] assertFloat4(@Nullable float[] out) {
if (out == null) out = new float[4]; if (out == null) out = new float[4];
@@ -25,7 +25,6 @@ import java.util.EnumSet;
import static com.google.android.filament.Asserts.assertFloat3In; import static com.google.android.filament.Asserts.assertFloat3In;
import static com.google.android.filament.Asserts.assertFloat4In; import static com.google.android.filament.Asserts.assertFloat4In;
import static com.google.android.filament.Colors.LinearColor;
import com.google.android.filament.proguard.UsedByNative; import com.google.android.filament.proguard.UsedByNative;
@@ -674,6 +673,21 @@ public class View {
return mDynamicResolution; return mDynamicResolution;
} }
/**
* Returns the last dynamic resolution scale factor used by this view. This value is updated
* when Renderer::render(View*) is called
* @param out A 2-float array where the value will be stored, or null in which case the array is
* allocated.
* @return A 2-float array containing the horizontal and the vertical scale factors
* @see Renderer#render(View)
*/
@NonNull @Size(min = 2)
public float[] getLastDynamicResolutionScale(@Nullable @Size(min = 2) float[] out) {
out = Asserts.assertFloat2(out);
nGetLastDynamicResolutionScale(getNativeObject(), out);
return out;
}
/** /**
* Sets the rendering quality for this view (e.g. color precision). * Sets the rendering quality for this view (e.g. color precision).
* *
@@ -1317,6 +1331,7 @@ public class View {
private static native void nSetDithering(long nativeView, int dithering); private static native void nSetDithering(long nativeView, int dithering);
private static native int nGetDithering(long nativeView); private static native int nGetDithering(long nativeView);
private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality); private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality);
private static native void nGetLastDynamicResolutionScale(long nativeView, float[] out);
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality); private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar); private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
private static native void nSetShadowType(long nativeView, int type); private static native void nSetShadowType(long nativeView, int type);
@@ -40,7 +40,21 @@ fun loadTexture(engine: Engine, resources: Resources, resourceId: Int, type: Tex
options.inPremultiplied = type == TextureType.COLOR options.inPremultiplied = type == TextureType.COLOR
val bitmap = BitmapFactory.decodeResource(resources, resourceId, options) val bitmap = BitmapFactory.decodeResource(resources, resourceId, options)
return buildTexture(engine, bitmap, type)
}
fun loadTexture(engine: Engine, bytes: ByteArray, type: TextureType, offset: Int = 0, length: Int = bytes.size): Texture {
val options = BitmapFactory.Options()
// Color is the only type of texture we want to pre-multiply with the alpha channel
// Pre-multiplication is the default behavior, so we need to turn it off here
options.inPremultiplied = type == TextureType.COLOR
val bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options)
return buildTexture(engine, bitmap, type)
}
private fun buildTexture(engine: Engine, bitmap: Bitmap, type: TextureType): Texture {
val texture = Texture.Builder() val texture = Texture.Builder()
.width(bitmap.width) .width(bitmap.width)
.height(bitmap.height) .height(bitmap.height)
@@ -22,6 +22,7 @@ object Utils {
/** /**
* Initializes the utils JNI layer. Must be called before using any utils functionality. * Initializes the utils JNI layer. Must be called before using any utils functionality.
*/ */
@JvmStatic
fun init() { fun init() {
// Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv. // Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv.
Filament.init() Filament.init()
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament GROUP=com.google.android.filament
VERSION_NAME=1.67.1 VERSION_NAME=1.67.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android. POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -17,12 +17,20 @@ filamentTools {
} }
// don't forget to update MainACtivity.kt when/if changing this. // don't forget to update MainACtivity.kt when/if changing this.
tasks.register('copyMesh', Copy) { tasks.register('copyDamagedHelmetGltf', Copy) {
from file("../../../third_party/models/DamagedHelmet/DamagedHelmet.glb")
into file("src/main/assets/models")
rename {String fileName -> "helmet.glb"}
}
// don't forget to update MainACtivity.kt when/if changing this.
tasks.register('copyBusterGltf', Copy) {
from "../../../third_party/models/BusterDrone" from "../../../third_party/models/BusterDrone"
into "src/main/assets/models" into "src/main/assets/models"
} }
preBuild.dependsOn copyMesh preBuild.dependsOn copyDamagedHelmetGltf
preBuild.dependsOn copyBusterGltf
clean.doFirst { clean.doFirst {
delete "src/main/assets" delete "src/main/assets"
+14 -3
View File
@@ -252,10 +252,21 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/utils/Spirv.h src/vulkan/utils/Spirv.h
src/vulkan/utils/StaticVector.h src/vulkan/utils/StaticVector.h
) )
if (LINUX OR WIN32) if (LINUX)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp) list(APPEND SRCS
src/vulkan/platform/VulkanPlatformLinux.cpp
include/backend/platforms/VulkanPlatformLinux.h
)
elseif (WIN32)
list(APPEND SRCS
include/backend/platforms/VulkanPlatformWindows.h
src/vulkan/platform/VulkanPlatformWindows.cpp
)
elseif (APPLE OR IOS) elseif (APPLE OR IOS)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformApple.mm) list(APPEND SRCS
include/backend/platforms/VulkanPlatformApple.h
src/vulkan/platform/VulkanPlatformApple.mm
)
elseif (ANDROID) elseif (ANDROID)
list(APPEND SRCS list(APPEND SRCS
include/backend/platforms/VulkanPlatformAndroid.h include/backend/platforms/VulkanPlatformAndroid.h
@@ -437,22 +437,19 @@ protected:
std::shared_ptr<VulkanCmdFence> fenceStatus; std::shared_ptr<VulkanCmdFence> fenceStatus;
}; };
virtual ExtensionSet getSwapchainInstanceExtensions() const;
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>; using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
virtual ExtensionSet getSwapchainInstanceExtensions() const = 0;
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept; uint64_t flags) const noexcept = 0;
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept; virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
/**
* Query if transient attachments are supported by the backend.
*/
bool isTransientAttachmentSupported() const noexcept;
private: private:
// Platform dependent helper methods
static ExtensionSet getSwapchainInstanceExtensionsImpl();
// Platform dependent helper methods
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
uint64_t flags) noexcept;
friend struct VulkanPlatformPrivate; friend struct VulkanPlatformPrivate;
}; };
@@ -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
+13 -3
View File
@@ -67,8 +67,12 @@
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include "backend/platforms/VulkanPlatformAndroid.h" #include "backend/platforms/VulkanPlatformAndroid.h"
#else #elif defined(__APPLE__)
#include "backend/platforms/VulkanPlatform.h" #include "backend/platforms/VulkanPlatformApple.h"
#elif defined(__linux__)
#include "backend/platforms/VulkanPlatformLinux.h"
#elif defined(WIN32)
#include "backend/platforms/VulkanPlatformWindows.h"
#endif #endif
#endif #endif
@@ -117,8 +121,14 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#if defined(__ANDROID__) #if defined(__ANDROID__)
return new VulkanPlatformAndroid(); return new VulkanPlatformAndroid();
#elif defined(__APPLE__)
return new VulkanPlatformApple();
#elif defined(__linux__)
return new VulkanPlatformLinux();
#elif defined(WIN32)
return new VulkanPlatformWindows();
#else #else
return new VulkanPlatform(); return nullptr;
#endif #endif
#else #else
return nullptr; return nullptr;
@@ -214,4 +214,10 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
// destroying any unused pipeline object. // destroying any unused pipeline object.
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS); static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
// Indicates if the backend must be setup to allow doing a RenderDoc capture.
//
// If this is true, the features not supported by RenderDoc must be disabled, otherwise
// when using RenderDoc the application will crash or will fail to do a capture.
constexpr static const int FVK_RENDERDOC_CAPTURE_MODE = false;
#endif #endif
@@ -154,6 +154,10 @@ public:
return mStagingBufferBypassEnabled; return mStagingBufferBypassEnabled;
} }
inline bool pipelineCreationFeedbackSupported() const noexcept {
return mPipelineCreationFeedbackSupported;
}
private: private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {}; VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = { VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -181,6 +185,7 @@ private:
bool mProtectedMemorySupported = false; bool mProtectedMemorySupported = false;
bool mIsUnifiedMemoryArchitecture = false; bool mIsUnifiedMemoryArchitecture = false;
bool mStagingBufferBypassEnabled = false; bool mStagingBufferBypassEnabled = false;
bool mPipelineCreationFeedbackSupported = false;
fvkutils::VkFormatList mDepthStencilFormats; fvkutils::VkFormatList mDepthStencilFormats;
fvkutils::VkFormatList mBlittableDepthStencilFormats; fvkutils::VkFormatList mBlittableDepthStencilFormats;
+1 -1
View File
@@ -231,7 +231,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager), mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
mPipelineLayoutCache(mPlatform->getDevice()), mPipelineLayoutCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice()), mPipelineCache(mPlatform->getDevice(), mContext),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()), mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(mContext, mResourceManager, mAllocator), mBufferCache(mContext, mResourceManager, mAllocator),
mFramebufferCache(mPlatform->getDevice()), mFramebufferCache(mPlatform->getDevice()),
@@ -34,8 +34,41 @@ using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPipelineCache::VulkanPipelineCache(VkDevice device) namespace {
: mDevice(device) {
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
void printPipelineFeedbackInfo(VkPipelineCreationFeedbackCreateInfo const& feedbackInfo) {
VkPipelineCreationFeedback const& pipelineInfo = *feedbackInfo.pPipelineCreationFeedback;
if (!(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
return;
}
bool const isCacheHit =
(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
FVK_LOGD << "Pipeline build stats - Cache hit: " << isCacheHit
<< ", Time: " << pipelineInfo.duration / 1000000.0 << "ms";
for (uint32_t i = 0; i < feedbackInfo.pipelineStageCreationFeedbackCount; ++i) {
VkPipelineCreationFeedback const& stageInfo = feedbackInfo.pPipelineStageCreationFeedbacks[i];
if (!(stageInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
continue;
}
bool const isVertexShader = (i == 0);
bool const isCacheHit = (stageInfo.flags &
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
FVK_LOGD << (isVertexShader ? "Vertex" : "Fragment")
<< " shader build stats - Cache hit: " << isCacheHit
<< ", Time: " << stageInfo.duration / 1000000.0 << "ms";
}
}
#endif
} // namespace
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VulkanContext const& context)
: mDevice(device),
mContext(context) {
VkPipelineCacheCreateInfo createInfo = { VkPipelineCacheCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
}; };
@@ -216,15 +249,40 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
} }
} }
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) #if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[0].module << ", " << shaderStages[1].module << ")"; << shaderStages[1].module << ")";
#endif
VkPipelineCreationFeedback stageFeedbacks[SHADER_MODULE_COUNT] = {};
VkPipelineCreationFeedback pipelineFeedback = {};
VkPipelineCreationFeedbackCreateInfo feedbackInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT,
.pNext = nullptr,
.pPipelineCreationFeedback = &pipelineFeedback,
.pipelineStageCreationFeedbackCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1,
.pPipelineStageCreationFeedbacks = stageFeedbacks,
};
if (mContext.pipelineCreationFeedbackSupported()) {
feedbackInfo.pNext = pipelineCreateInfo.pNext;
pipelineCreateInfo.pNext = &feedbackInfo;
}
#endif
PipelineCacheEntry cacheEntry = { PipelineCacheEntry cacheEntry = {
.lastUsed = mCurrentTime, .lastUsed = mCurrentTime,
}; };
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo, VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
VKALLOC, &cacheEntry.handle); VKALLOC, &cacheEntry.handle);
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[1].module << ")";
if (mContext.pipelineCreationFeedbackSupported()) {
printPipelineFeedbackInfo(feedbackInfo);
}
#endif
assert_invariant(error == VK_SUCCESS); assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) { if (error != VK_SUCCESS) {
FVK_LOGE << "vkCreateGraphicsPipelines error " << error; FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
@@ -86,7 +86,7 @@ public:
static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding."); static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding.");
VulkanPipelineCache(VkDevice device); VulkanPipelineCache(VkDevice device, VulkanContext const& context);
void bindLayout(VkPipelineLayout layout) noexcept; void bindLayout(VkPipelineLayout layout) noexcept;
@@ -210,6 +210,8 @@ private:
// Current bindings for the pipeline and descriptor sets. // Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {}; PipelineKey mBoundPipeline = {};
[[maybe_unused]] VulkanContext const& mContext;
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -228,6 +228,10 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
#endif #endif
VK_KHR_MULTIVIEW_EXTENSION_NAME, VK_KHR_MULTIVIEW_EXTENSION_NAME,
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME,
#endif
}; };
ExtensionSet exts; ExtensionSet exts;
// Identify supported physical device extensions // Identify supported physical device extensions
@@ -865,6 +869,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
if (!mImpl->mSharedContext) { if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME); context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mPipelineCreationFeedbackSupported =
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
} else { } else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext; VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported; context.mDebugUtilsSupported = scontext->debugUtilsSupported;
@@ -873,13 +879,16 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
// Check the availability of lazily allocated memory // Check the availability of lazily allocated memory
context.mLazilyAllocatedMemorySupported = false; context.mLazilyAllocatedMemorySupported = false;
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount; // RenderDoc doesn't support lazy allocated memory
++i) { if constexpr (!FVK_RENDERDOC_CAPTURE_MODE) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i]; for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) { ++i) {
context.mLazilyAllocatedMemorySupported = true; VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
break; context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
} }
} }
@@ -1022,13 +1031,8 @@ VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const no
return static_cast<VkExternalFenceHandleTypeFlagBits>(0); return static_cast<VkExternalFenceHandleTypeFlagBits>(0);
} }
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const { bool VulkanPlatform::isTransientAttachmentSupported() const noexcept {
return getSwapchainInstanceExtensionsImpl(); return mImpl->mContext.isLazilyAllocatedMemorySupported();
} }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow, } // namespace filament::backend
VkInstance instance, uint64_t flags) const noexcept {
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
}
}// namespace filament::backend
@@ -158,7 +158,47 @@ 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
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() { VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
if (__builtin_available(android 26, *)) { if (__builtin_available(android 26, *)) {
@@ -354,8 +394,6 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
.image = image, .image = image,
.buffer = VK_NULL_HANDLE, .buffer = VK_NULL_HANDLE,
}; };
VkPhysicalDeviceMemoryProperties memoryProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
VkMemoryPropertyFlags requiredMemoryFlags = VkMemoryPropertyFlags requiredMemoryFlags =
!isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE) !isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
@@ -365,8 +403,8 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT; requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
} }
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties, uint32_t const memoryTypeIndex = selectMemoryTypeForExternalImage(physicalDevice, device,
metadata.memoryTypeBits, requiredMemoryFlags); image, metadata.memoryTypeBits, requiredMemoryFlags);
VkMemoryAllocateInfo const allocInfo = { VkMemoryAllocateInfo const allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
@@ -502,13 +540,4 @@ bool VulkanPlatformAndroid::queryFrameTimestamps(SwapChain const* swapchain, uin
return vulkanSwapchain->queryFrameTimestamps(frameId, outFrameTimestamps); return vulkanSwapchain->queryFrameTimestamps(frameId, outFrameTimestamps);
} }
// Deprecated platform dependent helper methods
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept {
return SurfaceBundle{};
}
} // namespace filament::backend } // namespace filament::backend
@@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatformApple.h>
#include "vulkan/VulkanConstants.h" #include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanDriverFactory.h"
#include <utils/Panic.h> #include <utils/Panic.h>
@@ -36,15 +35,15 @@ using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { VulkanPlatform::ExtensionSet VulkanPlatformApple::getSwapchainInstanceExtensions() const {
ExtensionSet const ret = { ExtensionSet const ret = {
VK_EXT_METAL_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME,
}; };
return ret; return ret;
} }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow, VulkanPlatform::SurfaceBundle VulkanPlatformApple::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept { VkInstance instance, uint64_t flags) const noexcept {
VkSurfaceKHR surface; VkSurfaceKHR surface;
CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow; CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(mlayer) << "Unable to obtain Metal-backed layer."; FILAMENT_CHECK_POSTCONDITION(mlayer) << "Unable to obtain Metal-backed layer.";
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2023 The Android Open Source Project * Copyright (C) 2025 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatformLinux.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include "vulkan/VulkanConstants.h" #include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanDriverFactory.h"
#include <utils/Panic.h> #include <utils/Panic.h>
@@ -74,17 +73,13 @@
void* library = nullptr; void* library = nullptr;
} g_x11_vk; } g_x11_vk;
}// anonymous namespace }// anonymous namespace
#elif defined(WIN32)
// No platform specific includes
#else
// Not a supported Vulkan platform
#endif #endif
using namespace bluevk; using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { VulkanPlatform::ExtensionSet VulkanPlatformLinux::getSwapchainInstanceExtensions() const {
VulkanPlatform::ExtensionSet const ret = { VulkanPlatform::ExtensionSet const ret = {
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) #if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
@@ -95,15 +90,13 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
#if defined(FILAMENT_SUPPORTS_XLIB) #if defined(FILAMENT_SUPPORTS_XLIB)
VK_KHR_XLIB_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
#endif #endif
#elif defined(WIN32)
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
#endif #endif
}; };
return ret; return ret;
} }
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow, VulkanPlatform::SurfaceBundle VulkanPlatformLinux::createVkSurfaceKHR(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept { VkInstance instance, uint64_t flags) const noexcept {
VkSurfaceKHR surface; VkSurfaceKHR surface;
// On certain platforms, the extent of the surface cannot be queried from Vulkan. In those // On certain platforms, the extent of the surface cannot be queried from Vulkan. In those
@@ -182,26 +175,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativ
<< "vkCreateXlibSurfaceKHR error=" << static_cast<int32_t>(result); << "vkCreateXlibSurfaceKHR error=" << static_cast<int32_t>(result);
} }
#endif #endif
#elif defined(WIN32)
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
// to that of the native window we've been given. This isn't a perfect solution, because an
// application could create swap chains on multiple native windows with varying DPI-awareness,
// but even then, at least one of the windows would be guaranteed to work correctly.
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
VkWin32SurfaceCreateInfoKHR const createInfo = {
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
.hinstance = GetModuleHandle(nullptr),
.hwnd = (HWND) nativeWindow,
};
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
(VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateWin32SurfaceKHR failed."
<< " error=" << static_cast<int32_t>(result);
#endif #endif
return std::make_tuple(surface, extent); return std::make_tuple(surface, extent);
} }
@@ -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
@@ -63,7 +63,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
// WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a // WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a
// multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple // multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple
// of 4 by padding with zeros. // of 4 by padding with zeros.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor, void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor&& bufferDescriptor,
const uint32_t byteOffset, wgpu::Device const& device, const uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager) { WebGPUQueueManager* const webGPUQueueManager) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer) FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
@@ -89,24 +89,49 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
wgpu::BufferDescriptor descriptor{ wgpu::BufferDescriptor descriptor{
.label = "Filament WebGPU Staging Buffer", .label = "Filament WebGPU Staging Buffer",
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, .usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
.size = stagingBufferSize, .size = stagingBufferSize};
.mappedAtCreation = true };
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor); wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
void* mappedRange = stagingBuffer.GetMappedRange(); struct UserData final {
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size); uint32_t byteOffset;
size_t stagingBufferSize;
// Make sure the padded memory is set to 0 to have deterministic behaviors size_t remainder;
if (remainder != 0) { BufferDescriptor srcBufferDescriptor;
uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size; wgpu::Buffer stagingBuffer;
memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder); WebGPUQueueManager* const webGPUQueueManager;
} wgpu::Buffer dstBuffer;
};
stagingBuffer.Unmap(); auto userData = std::make_unique<UserData>(UserData{
.byteOffset = byteOffset,
// Copy the staging buffer contents to the destination buffer. .stagingBufferSize = stagingBufferSize,
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer, .remainder = remainder,
byteOffset, stagingBufferSize); .srcBufferDescriptor = std::move(bufferDescriptor),
.stagingBuffer = stagingBuffer,
.webGPUQueueManager = webGPUQueueManager,
.dstBuffer = mBuffer});
stagingBuffer.MapAsync(
wgpu::MapMode::Write, 0, stagingBufferSize, wgpu::CallbackMode::AllowProcessEvents,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
void* mappedRange = data->stagingBuffer.GetMappedRange();
memcpy(mappedRange, data->srcBufferDescriptor.buffer,
data->srcBufferDescriptor.size);
if (data->remainder != 0) {
uint8_t* paddingStart =
static_cast<uint8_t*>(mappedRange) + data->srcBufferDescriptor.size;
memset(paddingStart, 0,
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - data->remainder);
}
data->stagingBuffer.Unmap();
data->webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(
data->stagingBuffer, 0, data->dstBuffer, data->byteOffset,
data->stagingBufferSize);
} else {
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
}
},
userData.release());
} }
} // namespace filament::backend } // namespace filament::backend
@@ -39,7 +39,7 @@ public:
* happen after draw commands encoded in the encoder. Submitting any commands up to this point * happen after draw commands encoded in the encoder. Submitting any commands up to this point
* ensures the calls happen in the expected sequence. * ensures the calls happen in the expected sequence.
*/ */
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device, void updateGPUBuffer(BufferDescriptor&&, uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager); WebGPUQueueManager* const webGPUQueueManager);
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; } [[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
+3 -3
View File
@@ -856,7 +856,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
// draw calls are made. // draw calls are made.
flush(); flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle) handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager); ->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor)); scheduleDestroy(std::move(bufferDescriptor));
} }
@@ -867,14 +867,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
// draw calls are made. // draw calls are made.
flush(); flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle) handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager); ->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor)); scheduleDestroy(std::move(bufferDescriptor));
} }
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle, void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) { BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle) handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager); ->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
scheduleDestroy(std::move(bufferDescriptor)); scheduleDestroy(std::move(bufferDescriptor));
} }
@@ -21,6 +21,7 @@
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <thread> #include <thread>
#include <iostream>
namespace filament::backend { namespace filament::backend {
@@ -58,6 +59,7 @@ WebGPUQueueManager::WebGPUQueueManager(wgpu::Device const& device)
WebGPUQueueManager::~WebGPUQueueManager() = default; WebGPUQueueManager::~WebGPUQueueManager() = default;
wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() { wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
// std::unique_lock<std::mutex> lock(mLock);
if (!mCommandEncoder) { if (!mCommandEncoder) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
.label = "Filament Command Encoder", .label = "Filament Command Encoder",
@@ -90,7 +92,9 @@ std::shared_ptr<WebGPUSubmissionState> WebGPUQueueManager::getLatestSubmissionSt
} }
void WebGPUQueueManager::submit() { void WebGPUQueueManager::submit() {
// std::unique_lock<std::mutex> lock(mLock);
if (!mCommandEncoder) { if (!mCommandEncoder) {
std::cout << "Run Yu: no mCommandEncoder found!\n";
return; return;
} }
@@ -74,6 +74,7 @@ private:
wgpu::Queue mQueue; wgpu::Queue mQueue;
wgpu::CommandEncoder mCommandEncoder; wgpu::CommandEncoder mCommandEncoder;
std::shared_ptr<WebGPUSubmissionState> mLatestSubmissionState; std::shared_ptr<WebGPUSubmissionState> mLatestSubmissionState;
std::mutex mLock;
}; };
} // namespace filament::backend } // namespace filament::backend
+11 -11
View File
@@ -243,10 +243,10 @@ public:
/** Utility to set the projection matrix from the field-of-view. /** Utility to set the projection matrix from the field-of-view.
* *
* @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180. * @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180.
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far > \p near.
* @param direction direction of the \p fovInDegrees parameter. * @param direction direction of the \p fovInDegrees parameter.
* *
* @see Fov. * @see Fov.
*/ */
@@ -256,9 +256,9 @@ public:
/** Utility to set the projection matrix from the focal length. /** Utility to set the projection matrix from the focal length.
* *
* @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0. * @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0.
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane. \p near > 0.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far > \p near.
*/ */
void setLensProjection(double focalLengthInMillimeters, void setLensProjection(double focalLengthInMillimeters,
double aspect, double near, double far); double aspect, double near, double far);
@@ -270,8 +270,8 @@ public:
* that is all 3 axis are mapped to [-1, 1]. * that is all 3 axis are mapped to [-1, 1].
* *
* @param projection custom projection matrix used for rendering and culling * @param projection custom projection matrix used for rendering and culling
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far != \p near.
*/ */
void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept; void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept;
@@ -282,8 +282,8 @@ public:
* *
* @param projection custom projection matrix used for rendering * @param projection custom projection matrix used for rendering
* @param projectionForCulling custom projection matrix used for culling * @param projectionForCulling custom projection matrix used for culling
* @param near distance in world units from the camera to the near plane. \p near > 0. * @param near distance in world units from the camera to the near plane.
* @param far distance in world units from the camera to the far plane. \p far > \p near. * @param far distance in world units from the camera to the far plane. \p far != \p near.
*/ */
void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling,
double near, double far) noexcept; double near, double far) noexcept;
+8
View File
@@ -541,6 +541,14 @@ public:
*/ */
DynamicResolutionOptions getDynamicResolutionOptions() const noexcept; DynamicResolutionOptions getDynamicResolutionOptions() const noexcept;
/**
* Returns the last dynamic resolution scale factor used by this view. This value is updated
* when Renderer::render(View*) is called
* @return a float2 where x is the horizontal and y the vertical scale factor.
* @see Renderer::render
*/
math::float2 getLastDynamicResolutionScale() const noexcept;
/** /**
* Sets the rendering quality for this view. Refer to RenderQuality for more * Sets the rendering quality for this view. Refer to RenderQuality for more
* information about the different settings available. * information about the different settings available.
+28 -5
View File
@@ -16,14 +16,30 @@
#include "details/Camera.h" #include "details/Camera.h"
#include <filament/Camera.h>
#include <math/mat4.h> #include <math/mat4.h>
#include <utils/Panic.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <cstddef>
#include <cstdint>
namespace filament { namespace filament {
using namespace math; using namespace math;
void Camera::setProjection(double const fovInDegrees, double const aspect, double const near, double const far, void Camera::setProjection(double const fovInDegrees, double const aspect,
Fov const direction) { double const near, double const far, Fov const direction) {
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
<< "Camera preconditions not met in setProjection(): near <= 0 or far <= near, near="
<< near << ", far=" << far;
setCustomProjection( setCustomProjection(
projection(direction, fovInDegrees, aspect, near), projection(direction, fovInDegrees, aspect, near),
projection(direction, fovInDegrees, aspect, near, far), projection(direction, fovInDegrees, aspect, near, far),
@@ -32,6 +48,11 @@ void Camera::setProjection(double const fovInDegrees, double const aspect, doubl
void Camera::setLensProjection(double const focalLengthInMillimeters, void Camera::setLensProjection(double const focalLengthInMillimeters,
double const aspect, double const near, double const far) { double const aspect, double const near, double const far) {
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
<< "Camera preconditions not met in setLensProjection(): near <= 0 or far <= near, near="
<< near << ", far=" << far;
setCustomProjection( setCustomProjection(
projection(focalLengthInMillimeters, aspect, near), projection(focalLengthInMillimeters, aspect, near),
projection(focalLengthInMillimeters, aspect, near, far), projection(focalLengthInMillimeters, aspect, near, far),
@@ -149,8 +170,9 @@ utils::Entity Camera::getEntity() const noexcept {
return downcast(this)->getEntity(); return downcast(this)->getEntity();
} }
void Camera::setExposure(float const aperture, float const shutterSpeed, float const ISO) noexcept { void Camera::setExposure(
downcast(this)->setExposure(aperture, shutterSpeed, ISO); float const aperture, float const shutterSpeed, float const sensitivity) noexcept {
downcast(this)->setExposure(aperture, shutterSpeed, sensitivity);
} }
float Camera::getAperture() const noexcept { float Camera::getAperture() const noexcept {
@@ -177,7 +199,8 @@ double Camera::getFocalLength() const noexcept {
return downcast(this)->getFocalLength(); return downcast(this)->getFocalLength();
} }
double Camera::computeEffectiveFocalLength(double const focalLength, double const focusDistance) noexcept { double Camera::computeEffectiveFocalLength(
double const focalLength, double const focusDistance) noexcept {
return FCamera::computeEffectiveFocalLength(focalLength, focusDistance); return FCamera::computeEffectiveFocalLength(focalLength, focusDistance);
} }
+24 -2
View File
@@ -226,6 +226,8 @@ bool Froxelizer::prepare(
filament::Viewport const& viewport, filament::Viewport const& viewport,
const mat4f& projection, float const projectionNear, float const projectionFar, const mat4f& projection, float const projectionNear, float const projectionFar,
float4 const& clipTransform) noexcept { float4 const& clipTransform) noexcept {
assert_invariant(projectionFar > projectionNear);
assert_invariant(projectionNear > 0);
setViewport(viewport); setViewport(viewport);
setProjection(projection, projectionNear, projectionFar); setProjection(projection, projectionNear, projectionFar);
@@ -383,8 +385,28 @@ bool Froxelizer::update() noexcept {
bool uniformsNeedUpdating = false; bool uniformsNeedUpdating = false;
if (UTILS_UNLIKELY(mDirtyFlags & (OPTIONS_CHANGED|PROJECTION_CHANGED))) { if (UTILS_UNLIKELY(mDirtyFlags & (OPTIONS_CHANGED|PROJECTION_CHANGED))) {
float const zLightFar = clamp(mUserZLightFar, mNear, mFar);
float zLightNear = clamp(mUserZLightNear, mNear, mFar); // sanitize the user's near/far
float zLightNear = mUserZLightNear;
float zLightFar = mUserZLightFar;
if (zLightFar == zLightNear) {
zLightNear = mNear;
zLightFar = mFar;
}
if (zLightFar < zLightNear) {
std::swap(zLightFar, zLightNear);
}
if (zLightNear < mNear || zLightNear >= mFar) {
zLightNear = mNear;
}
if (zLightFar > mFar || zLightFar <= mNear) {
zLightFar = mFar;
}
assert_invariant(zLightNear < zLightFar);
assert_invariant(zLightNear >= mNear && zLightNear <= mFar);
assert_invariant(zLightFar <= mFar && zLightNear >= mNear);
zLightNear = std::min(zLightNear, zLightFar); zLightNear = std::min(zLightNear, zLightFar);
if (zLightFar != mZLightFar || zLightNear != mZLightNear) { if (zLightFar != mZLightFar || zLightNear != mZLightNear) {
mDirtyFlags |= VIEWPORT_CHANGED; mDirtyFlags |= VIEWPORT_CHANGED;
+23 -19
View File
@@ -258,6 +258,10 @@ void PostProcessManager::bindPerRenderableDescriptorSet(DriverApi& driver) const
{ { 0, 0 }, driver }); { { 0, 0 }, driver });
} }
UboManager* PostProcessManager::getUboManager() const noexcept {
return mEngine.getUboManager();
}
UTILS_NOINLINE UTILS_NOINLINE
void PostProcessManager::registerPostProcessMaterial(std::string_view const name, void PostProcessManager::registerPostProcessMaterial(std::string_view const name,
StaticMaterialInfo const& info) { StaticMaterialInfo const& info) {
@@ -513,7 +517,7 @@ UTILS_NOINLINE
void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver, void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver,
FrameGraphResources::RenderPassInfo const& out, FMaterialInstance const* mi, FrameGraphResources::RenderPassInfo const& out, FMaterialInstance const* mi,
PostProcessVariant const variant) const noexcept { PostProcessVariant const variant) const noexcept {
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
FMaterial const* const ma = mi->getMaterial(); FMaterial const* const ma = mi->getMaterial();
PipelineState const pipeline = getPipelineState(ma, variant); PipelineState const pipeline = getPipelineState(ma, variant);
@@ -645,7 +649,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
auto th = driver.createTextureView(in, level, 1); auto th = driver.createTextureView(in, level, 1);
mi->setParameter("depth", th, SamplerParams{ mi->setParameter("depth", th, SamplerParams{
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuad(out, pipeline, driver); renderFullScreenQuad(out, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL); DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
@@ -1079,7 +1083,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
mi->setParameter("ssctRayCount", mi->setParameter("ssctRayCount",
float2{ options.ssct.rayCount, 1.0f / float(options.ssct.rayCount) }); float2{ options.ssct.rayCount, 1.0f / float(options.ssct.rayCount) });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -1200,7 +1204,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
mi->setParameter("sampleCount", kGaussianCount); mi->setParameter("sampleCount", kGaussianCount);
mi->setParameter("farPlaneOverEdgeDistance", -zf / config.bilateralThreshold); mi->setParameter("farPlaneOverEdgeDistance", -zf / config.bilateralThreshold);
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -1895,7 +1899,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres? mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres?
mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h }); mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuad(out, pipeline, driver); renderFullScreenQuad(out, pipeline, driver);
@@ -2425,7 +2429,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
mi->setParameter("source", hwOutView, SamplerParams{ mi->setParameter("source", hwOutView, SamplerParams{
.filterMag = SamplerMagFilter::LINEAR, .filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST});
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuad(hwDstRT, pipeline, driver); renderFullScreenQuad(hwDstRT, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL); DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
@@ -2533,7 +2537,7 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
FMaterialInstance* const mi = FMaterialInstance* const mi =
configureColorGradingMaterial(material, colorGrading, colorGradingConfig, configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
vignetteOptions, width, height); vignetteOptions, width, height);
mi->commit(driver); mi->commit(driver, getUboManager());
} }
void PostProcessManager::colorGradingSubpass(DriverApi& driver, void PostProcessManager::colorGradingSubpass(DriverApi& driver,
@@ -2566,7 +2570,7 @@ void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomRe
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma); auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.customResolve = fixedIndex; mFixedMaterialInstanceIndex.customResolve = fixedIndex;
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f), mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
mi->commit(driver); mi->commit(driver, getUboManager());
material.getMaterial(mEngine); material.getMaterial(mEngine);
} }
@@ -2619,7 +2623,7 @@ void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver) noexcep
auto ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE); auto ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma); auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.clearDepth = fixedIndex; mFixedMaterialInstanceIndex.clearDepth = fixedIndex;
mi->commit(driver); mi->commit(driver, getUboManager());
material.getMaterial(mEngine); material.getMaterial(mEngine);
} }
@@ -3111,7 +3115,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
mat4f{ historyProjection * inverse(current.projection) } * mat4f{ historyProjection * inverse(current.projection) } *
normalizedToClip); normalizedToClip);
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
if (colorGradingConfig.asSubpass) { if (colorGradingConfig.asSubpass) {
@@ -3196,7 +3200,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
mi->setParameter("resolution", float4{ mi->setParameter("resolution", float4{
outputDesc.width, outputDesc.height, outputDesc.width, outputDesc.height,
1.0f / outputDesc.width, 1.0f / outputDesc.height }); 1.0f / outputDesc.width, 1.0f / outputDesc.height });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(material.getMaterial(mEngine), variant); auto pipeline = getPipelineState(material.getMaterial(mEngine), variant);
@@ -3287,7 +3291,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
float(vp.width) / inputDesc.width, float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height float(vp.height) / inputDesc.height
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto out = resources.getRenderPassInfo(); auto out = resources.getRenderPassInfo();
@@ -3374,7 +3378,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleSGSR1(FrameGraph& fg,
float(inputDesc.height) float(inputDesc.height)
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto const out = resources.getRenderPassInfo(); auto const out = resources.getRenderPassInfo();
@@ -3469,7 +3473,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
mi->setParameter("resolution", mi->setParameter("resolution",
float4{ outputDesc.width, outputDesc.height, float4{ outputDesc.width, outputDesc.height,
1.0f / outputDesc.width, 1.0f / outputDesc.height }); 1.0f / outputDesc.width, 1.0f / outputDesc.height });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
} }
@@ -3496,7 +3500,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
float(vp.width) / inputDesc.width, float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height float(vp.height) / inputDesc.height
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
} }
@@ -3582,7 +3586,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
if (layer) { if (layer) {
mi->setParameter("layerIndex", layer); mi->setParameter("layerIndex", layer);
} }
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -3840,7 +3844,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
}); });
mi->setParameter("layer", uint32_t(layer)); mi->setParameter("layer", uint32_t(layer));
mi->setParameter("uvscale", 1.0f / float(dim)); mi->setParameter("uvscale", 1.0f / float(dim));
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
renderFullScreenQuadWithScissor(out, pipeline, scissor, driver); renderFullScreenQuadWithScissor(out, pipeline, scissor, driver);
@@ -3942,7 +3946,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
float(vp.width) / inputDesc.width, float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height float(vp.height) / inputDesc.height
}); });
mi->commit(driver); mi->commit(driver, getUboManager());
mi->use(driver); mi->use(driver);
auto pipeline = getPipelineState(ma); auto pipeline = getPipelineState(ma);
@@ -3959,7 +3963,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
// Render all layers of the texture to the screen side-by-side. // Render all layers of the texture to the screen side-by-side.
for (uint32_t i = 0; i < inputTextureDesc.depth; ++i) { for (uint32_t i = 0; i < inputTextureDesc.depth; ++i) {
mi->setParameter("layerIndex", i); mi->setParameter("layerIndex", i);
mi->commit(driver); mi->commit(driver, getUboManager());
renderFullScreenQuad(out, pipeline, driver); renderFullScreenQuad(out, pipeline, driver);
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL); DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
// From the second draw, don't clear the targetbuffer. // From the second draw, don't clear the targetbuffer.
+3
View File
@@ -65,6 +65,7 @@ class FMaterialInstance;
class FrameGraph; class FrameGraph;
class RenderPass; class RenderPass;
class RenderPassBuilder; class RenderPassBuilder;
class UboManager;
struct CameraInfo; struct CameraInfo;
class PostProcessManager { class PostProcessManager {
@@ -422,6 +423,8 @@ private:
return getMaterialInstance(ma); return getMaterialInstance(ma);
} }
UboManager* getUboManager() const noexcept;
backend::RenderPrimitiveHandle mFullScreenQuadRph; backend::RenderPrimitiveHandle mFullScreenQuadRph;
backend::VertexBufferInfoHandle mFullScreenQuadVbih; backend::VertexBufferInfoHandle mFullScreenQuadVbih;
backend::DescriptorSetLayoutHandle mPerRenderableDslh; backend::DescriptorSetLayoutHandle mPerRenderableDslh;
+25 -15
View File
@@ -1012,6 +1012,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
uint32_t const index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT; uint32_t const index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT;
assert_invariant(index < mCustomCommands.size()); assert_invariant(index < mCustomCommands.size());
pCustomCommands[index](); pCustomCommands[index]();
currentPipeline = {};
currentPrimitiveHandle ={};
continue; continue;
} }
@@ -1069,26 +1071,34 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] = pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] =
ma->getDescriptorSetLayout(info.materialVariant).getHandle(); ma->getDescriptorSetLayout(info.materialVariant).getHandle();
// If we have a ColorPassDescriptorSet we use it to bind the per-view
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) { // descriptor-set (ideally only if it changed).
// It is possible to get a post-process material here (even though it's // If we don't, it means the descriptor-set is already bound and the layout we
// not technically a public API yet, it is used by the IBLPrefilterLibrary. // got from the material above should match. This is the case for situations
// Ideally we would have a more formal compute API). In this case, we need // where we have a known per-view descriptor-set layout,
// to set the post-process descriptor-set. // e.g.: postfx, shadow-maps, ssr and structure passes.
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver); if (mColorPassDescriptorSet) {
} else { if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
// If we have a ColorPassDescriptorSet we use it to bind the per-view // It is possible to get a post-process material here (even though it's
// descriptor-set (ideally only if it changed). If we don't, it means // not technically a public API yet, it is used by the IBLPrefilterLibrary.
// the descriptor-set is already bound and the layout we got from the // Ideally we would have a more formal compute API). In this case, we need
// material above should match. This is the case for situations where we // to set the post-process descriptor-set.
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
// structure passes. } else {
if (mColorPassDescriptorSet) {
// We have a ColorPassDescriptorSet, we need to go through it for binding // We have a ColorPassDescriptorSet, we need to go through it for binding
// the per-view descriptor-set because its layout can change based on the // the per-view descriptor-set because its layout can change based on the
// material. // material.
mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex()); mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex());
} }
} else {
// if we're here it means the per-view descriptor set is constant and
// already set. This will be the case for postfx, ssr, structure and
// shadow passes. All these passes use a static descriptor set layout
// (albeit potentially different for each pass). In particular the
// per-view UBO must be compatible for all material domains.
// This is the case by construction for postfx, ssr. However, shadows
// and structure have their own UBO, but it's content is (must be)
// compatible with POST_PROCESS and COMPUTE materials.
} }
// Each MaterialInstance has its own descriptor set. This binds it. // Each MaterialInstance has its own descriptor set. This binds it.
+4
View File
@@ -178,6 +178,10 @@ View::DynamicResolutionOptions View::getDynamicResolutionOptions() const noexcep
return downcast(this)->getDynamicResolutionOptions(); return downcast(this)->getDynamicResolutionOptions();
} }
math::float2 View::getLastDynamicResolutionScale() const noexcept {
return downcast(this)->getLastDynamicResolutionScale();
}
void View::setRenderQuality(const RenderQuality& renderQuality) noexcept { void View::setRenderQuality(const RenderQuality& renderQuality) noexcept {
downcast(this)->setRenderQuality(renderQuality); downcast(this)->setRenderQuality(renderQuality);
} }
+15 -2
View File
@@ -142,7 +142,11 @@ void BufferAllocator::retire(AllocationId id) {
InternalSlotNode* targetNode = getNodeById(id); InternalSlotNode* targetNode = getNodeById(id);
assert_invariant(targetNode != nullptr); assert_invariant(targetNode != nullptr);
targetNode->slot.isAllocated = false; Slot& slot = targetNode->slot;
slot.isAllocated = false;
if (slot.gpuUseCount == 0) {
mHasPendingFrees = true;
}
} }
void BufferAllocator::acquireGpu(AllocationId id) { void BufferAllocator::acquireGpu(AllocationId id) {
@@ -157,10 +161,18 @@ void BufferAllocator::releaseGpu(AllocationId id) {
assert_invariant(targetNode != nullptr); assert_invariant(targetNode != nullptr);
assert_invariant(targetNode->slot.gpuUseCount > 0); assert_invariant(targetNode->slot.gpuUseCount > 0);
targetNode->slot.gpuUseCount--; Slot& slot = targetNode->slot;
slot.gpuUseCount--;
if (slot.gpuUseCount == 0 && !slot.isAllocated) {
mHasPendingFrees = true;
}
} }
void BufferAllocator::releaseFreeSlots() { void BufferAllocator::releaseFreeSlots() {
if (!mHasPendingFrees) {
return;
}
auto curr = mSlotPool.begin(); auto curr = mSlotPool.begin();
while (curr != mSlotPool.end()) { while (curr != mSlotPool.end()) {
if (!curr->slot.isFree()) { if (!curr->slot.isFree()) {
@@ -199,6 +211,7 @@ void BufferAllocator::releaseFreeSlots() {
curr = next; curr = next;
} }
mHasPendingFrees = false;
} }
BufferAllocator::allocation_size_t BufferAllocator::getTotalSize() const noexcept { BufferAllocator::allocation_size_t BufferAllocator::getTotalSize() const noexcept {
+2 -1
View File
@@ -113,8 +113,9 @@ private:
[[nodiscard]] InternalSlotNode* getNodeById(AllocationId id) const noexcept; [[nodiscard]] InternalSlotNode* getNodeById(AllocationId id) const noexcept;
bool mHasPendingFrees = false;
allocation_size_t mTotalSize; allocation_size_t mTotalSize;
const allocation_size_t mSlotSize; // Size of a single slot in bytes. const allocation_size_t mSlotSize; // Size of a single slot in bytes
std::list<InternalSlotNode> mSlotPool; // All slots, including both allocated and freed std::list<InternalSlotNode> mSlotPool; // All slots, including both allocated and freed
std::multimap</*slot size*/allocation_size_t, InternalSlotNode*> mFreeList; std::multimap</*slot size*/allocation_size_t, InternalSlotNode*> mFreeList;
std::unordered_map</*slot offset*/allocation_size_t, InternalSlotNode*> mOffsetMap; std::unordered_map</*slot offset*/allocation_size_t, InternalSlotNode*> mOffsetMap;
+31 -14
View File
@@ -24,27 +24,35 @@
#include <filament/Camera.h> #include <filament/Camera.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Panic.h> #include <utils/Panic.h>
#include <math/scalar.h> #include <math/scalar.h>
#include <math/mat4.h>
#include <math/vec2.h> #include <math/vec2.h>
#include <math/vec3.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <limits>
using namespace filament::math; using namespace filament::math;
using namespace utils; using namespace utils;
namespace filament { namespace filament {
static constexpr const float MIN_APERTURE = 0.5f; static constexpr float MIN_APERTURE = 0.5f;
static constexpr const float MAX_APERTURE = 64.0f; static constexpr float MAX_APERTURE = 64.0f;
static constexpr const float MIN_SHUTTER_SPEED = 1.0f / 25000.0f; static constexpr float MIN_SHUTTER_SPEED = 1.0f / 25000.0f;
static constexpr const float MAX_SHUTTER_SPEED = 60.0f; static constexpr float MAX_SHUTTER_SPEED = 60.0f;
static constexpr const float MIN_SENSITIVITY = 10.0f; static constexpr float MIN_SENSITIVITY = 10.0f;
static constexpr const float MAX_SENSITIVITY = 204800.0f; static constexpr float MAX_SENSITIVITY = 204800.0f;
FCamera::FCamera(FEngine& engine, Entity const e) FCamera::FCamera(FEngine& engine, Entity const e)
: mEngine(engine), : mEngine(engine),
mEntity(e) { mEntity(e) {
setProjection(Projection::PERSPECTIVE, -1.0, 1.0, -1.0, 1.0, 0.1, 1.0);
} }
mat4 FCamera::projection(Fov const direction, double const fovInDegrees, mat4 FCamera::projection(Fov const direction, double const fovInDegrees,
@@ -84,12 +92,16 @@ mat4 FCamera::projection(double const focalLengthInMillimeters,
* All methods for setting the projection funnel through here * All methods for setting the projection funnel through here
*/ */
void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p, void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& projection,
mat4 const& c, double const near, double const far) noexcept { mat4 const& projectionForCulling, double const near, double const far) noexcept {
FILAMENT_CHECK_PRECONDITION(near != far)
<< "Camera preconditions not met in setCustomProjection(): near = far = " << near;
for (auto& eyeProjection: mEyeProjection) { for (auto& eyeProjection: mEyeProjection) {
eyeProjection = p; eyeProjection = projection;
} }
mProjectionForCulling = c; mProjectionForCulling = projectionForCulling;
mNear = near; mNear = near;
mFar = far; mFar = far;
} }
@@ -97,10 +109,15 @@ void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p,
void UTILS_NOINLINE FCamera::setCustomEyeProjection(mat4 const* projection, size_t const count, void UTILS_NOINLINE FCamera::setCustomEyeProjection(mat4 const* projection, size_t const count,
mat4 const& projectionForCulling, double const near, double const far) { mat4 const& projectionForCulling, double const near, double const far) {
const Engine::Config& config = mEngine.getConfig(); const Engine::Config& config = mEngine.getConfig();
FILAMENT_CHECK_PRECONDITION(near != far)
<< "Camera preconditions not met in setCustomEyeProjection(): near = far = " << near;
FILAMENT_CHECK_PRECONDITION(count >= config.stereoscopicEyeCount) FILAMENT_CHECK_PRECONDITION(count >= config.stereoscopicEyeCount)
<< "All eye projections must be supplied together, count must be >= " << "All eye projections must be supplied together, count must be >= "
"config.stereoscopicEyeCount (" "config.stereoscopicEyeCount ("
<< config.stereoscopicEyeCount << ")"; << config.stereoscopicEyeCount << ")";
for (int i = 0; i < config.stereoscopicEyeCount; i++) { for (int i = 0; i < config.stereoscopicEyeCount; i++) {
mEyeProjection[i] = projection[i]; mEyeProjection[i] = projection[i];
} }
@@ -290,10 +307,10 @@ CameraInfo::CameraInfo(FCamera const& camera,
model = mat4f{ modelMatrix }; model = mat4f{ modelMatrix };
view = mat4f{ inverse(modelMatrix) }; view = mat4f{ inverse(modelMatrix) };
worldTransform = inWorldTransform; worldTransform = inWorldTransform;
zn = (float)camera.getNear(); zn = float(camera.getNear());
zf = (float)camera.getCullingFar(); zf = float(camera.getCullingFar());
ev100 = Exposure::ev100(camera); ev100 = Exposure::ev100(camera);
f = (float)camera.getFocalLength(); f = float(camera.getFocalLength());
A = f / camera.getAperture(); A = f / camera.getAperture();
d = std::max(zn, camera.getFocusDistance()); d = std::max(zn, camera.getFocusDistance());
} }
+53 -6
View File
@@ -468,11 +468,26 @@ void FEngine::init() {
} }
mDefaultMaterial = downcast(defaultMaterialBuilder.build(*this)); mDefaultMaterial = downcast(defaultMaterialBuilder.build(*this));
} }
// We must commit the default material instance here. It may not be used in a scene, but its // We must commit the default material instance here. It may not be used in a scene, but its
// descriptor set may still be used for shared variants. // descriptor set may still be used for shared variants.
mDefaultMaterial->getDefaultInstance()->commit(driverApi); //
// Note that this material instance is instantiated before the creation of UboManager, so at
// this point `isUboBatchingEnabled` is `false`, and it will fall back to individual UBO
// automatically.
mDefaultMaterial->getDefaultInstance()->commit(driverApi, mUboManager);
if (UTILS_UNLIKELY(getSupportedFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1)) { if (UTILS_UNLIKELY(getSupportedFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1)) {
// UBO batching is not supported in feature level 0
if (features.material.enable_material_instance_uniform_batching) {
// Ubo size of each material instance is at least 16 bytes.
constexpr BufferAllocator::allocation_size_t minSlotSize = 16;
auto const uboOffsetAlignment = static_cast<BufferAllocator::allocation_size_t>(
driverApi.getUniformBufferOffsetAlignment());
BufferAllocator::allocation_size_t slotSize = std::max(minSlotSize, uboOffsetAlignment);
mUboManager = new UboManager(getDriverApi(), slotSize, mConfig.sharedUboInitialSizeInBytes);
}
mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this)); mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this));
constexpr float3 dummyPositions[1] = {}; constexpr float3 dummyPositions[1] = {};
@@ -653,6 +668,12 @@ void FEngine::shutdown() {
driver.destroyRenderTarget(std::move(mDefaultRenderTarget)); driver.destroyRenderTarget(std::move(mDefaultRenderTarget));
if (isUboBatchingEnabled()) {
mUboManager->terminate(driver);
delete mUboManager;
mUboManager = nullptr;
}
/* /*
* Shutdown the backend... * Shutdown the backend...
*/ */
@@ -693,18 +714,31 @@ void FEngine::prepare() {
// UBOs that are visible only. It's not such a big issue because the actual upload() is // UBOs that are visible only. It's not such a big issue because the actual upload() is
// skipped if the UBO hasn't changed. Still we could have a lot of these. // skipped if the UBO hasn't changed. Still we could have a lot of these.
DriverApi& driver = getDriverApi(); DriverApi& driver = getDriverApi();
const bool useUboBatching = isUboBatchingEnabled();
if (useUboBatching) {
assert_invariant(mUboManager != nullptr);
mUboManager->beginFrame(driver, mMaterialInstances);
}
UboManager* uboManager = mUboManager;
for (auto& materialInstanceList: mMaterialInstances) { for (auto& materialInstanceList: mMaterialInstances) {
materialInstanceList.second.forEach([&driver](FMaterialInstance* item) { materialInstanceList.second.forEach([&driver, uboManager](FMaterialInstance* item) {
// post-process materials instances must be commited explicitly because their // post-process materials instances must be commited explicitly because their
// parameters are typically not set at this point in time. // parameters are typically not set at this point in time.
if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) { if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) {
item->commitStreamUniformAssociations(driver); item->commitStreamUniformAssociations(driver);
item->commit(driver); item->commit(driver, uboManager);
} }
}); });
} }
if (useUboBatching) {
assert_invariant(mUboManager != nullptr);
getUboManager()->finishBeginFrame(getDriverApi());
}
mMaterials.forEach([](FMaterial* material) { mMaterials.forEach([](FMaterial* material) {
#if FILAMENT_ENABLE_MATDBG // NOLINT(*-include-cleaner) #if FILAMENT_ENABLE_MATDBG // NOLINT(*-include-cleaner)
material->checkProgramEdits(); material->checkProgramEdits();
@@ -721,6 +755,13 @@ void FEngine::gc() {
mCameraManager.gc(*this, em); mCameraManager.gc(*this, em);
} }
void FEngine::submitFrame() {
if (isUboBatchingEnabled()) {
DriverApi& driver = getDriverApi();
getUboManager()->endFrame(driver, getMaterialInstanceResourceList());
}
}
void FEngine::flush() { void FEngine::flush() {
// flush the command buffer // flush the command buffer
flushCommandBuffer(mCommandBufferQueue); flushCommandBuffer(mCommandBufferQueue);
@@ -951,9 +992,10 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
return p; return p;
} }
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name,
const char* name) noexcept { UboBatchingMode batchingMode) noexcept {
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name); FMaterialInstance* p =
mHeapAllocator.make<FMaterialInstance>(*this, material, name, batchingMode);
if (UTILS_LIKELY(p)) { if (UTILS_LIKELY(p)) {
auto pos = mMaterialInstances.emplace(material, "MaterialInstance"); auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
pos.first->second.insert(p); pos.first->second.insert(p);
@@ -1243,6 +1285,11 @@ UTILS_NOINLINE
bool FEngine::destroy(const FMaterialInstance* p) { bool FEngine::destroy(const FMaterialInstance* p) {
if (p == nullptr) return true; if (p == nullptr) return true;
if (p->isUsingUboBatching()) {
assert_invariant(isUboBatchingEnabled());
mUboManager->retireSlot(p->getAllocationId());
}
// Check that the material instance we're destroying is not in use in the RenderableManager // Check that the material instance we're destroying is not in use in the RenderableManager
// To do this, we currently need to inspect all render primitives in the RenderableManager // To do this, we currently need to inspect all render primitives in the RenderableManager
EntityManager const& em = mEntityManager; EntityManager const& em = mEntityManager;
+26 -5
View File
@@ -21,16 +21,17 @@
#include "Allocators.h" #include "Allocators.h"
#include "DFG.h" #include "DFG.h"
#include "PostProcessManager.h"
#include "ResourceList.h"
#include "HwDescriptorSetLayoutFactory.h" #include "HwDescriptorSetLayoutFactory.h"
#include "HwVertexBufferInfoFactory.h" #include "HwVertexBufferInfoFactory.h"
#include "MaterialCache.h" #include "MaterialCache.h"
#include "PostProcessManager.h"
#include "ResourceList.h"
#include "UboManager.h"
#include "components/CameraManager.h" #include "components/CameraManager.h"
#include "components/LightManager.h" #include "components/LightManager.h"
#include "components/TransformManager.h"
#include "components/RenderableManager.h" #include "components/RenderableManager.h"
#include "components/TransformManager.h"
#include "ds/DescriptorSetLayout.h" #include "ds/DescriptorSetLayout.h"
@@ -263,6 +264,14 @@ public:
return mBackend; return mBackend;
} }
bool isUboBatchingEnabled() const noexcept {
return mUboManager != nullptr;
}
UboManager* getUboManager() noexcept {
return mUboManager;
}
Platform* getPlatform() const noexcept { Platform* getPlatform() const noexcept {
return mPlatform; return mPlatform;
} }
@@ -336,11 +345,21 @@ public:
FRenderer* createRenderer() noexcept; FRenderer* createRenderer() noexcept;
// Defines whether a material instance should use UBO batching or not.
enum class UboBatchingMode {
// For default, it follows the engine settings.
// If UBO batching is enabled on the engine and the material domain is not SURFACE, it
// turns on the UBO batching. Otherwise, it turns off the UBO batching.
DEFAULT,
NO_UBO_BATCHING,
UBO_BATCHING
};
FMaterialInstance* createMaterialInstance(const FMaterial* material, FMaterialInstance* createMaterialInstance(const FMaterial* material,
const FMaterialInstance* other, const char* name) noexcept; const FMaterialInstance* other, const char* name) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material, FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name,
const char* name) noexcept; UboBatchingMode batchingMode) noexcept;
FScene* createScene() noexcept; FScene* createScene() noexcept;
FView* createView() noexcept; FView* createView() noexcept;
@@ -446,6 +465,7 @@ public:
void prepare(); void prepare();
void gc(); void gc();
void submitFrame();
using ShaderContent = utils::FixedCapacityVector<uint8_t>; using ShaderContent = utils::FixedCapacityVector<uint8_t>;
@@ -650,6 +670,7 @@ private:
uint32_t mFlushCounter = 0; uint32_t mFlushCounter = 0;
UboManager* mUboManager = nullptr;
RootArenaScope::Arena mPerRenderPassArena; RootArenaScope::Arena mPerRenderPassArena;
HeapAllocatorArena mHeapAllocator; HeapAllocatorArena mHeapAllocator;
+13 -11
View File
@@ -73,6 +73,7 @@ namespace filament {
using namespace backend; using namespace backend;
using namespace filaflat; using namespace filaflat;
using namespace utils; using namespace utils;
using UboBatchingMode = FEngine::UboBatchingMode;
struct Material::BuilderDetails { struct Material::BuilderDetails {
const void* mPayload = nullptr; const void* mPayload = nullptr;
@@ -217,16 +218,17 @@ void FMaterial::terminate(FEngine& engine) {
filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout( filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept { Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
if (Variant::isValidDepthVariant(variant)) { if (mDefinition.materialDomain == MaterialDomain::SURFACE) {
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE); // `variant` is only sensical for MaterialDomain::SURFACE
return mEngine.getPerViewDescriptorSetLayoutDepthVariant(); if (Variant::isValidDepthVariant(variant)) {
} return mEngine.getPerViewDescriptorSetLayoutDepthVariant();
if (Variant::isSSRVariant(variant)) { }
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE); if (Variant::isSSRVariant(variant)) {
return mEngine.getPerViewDescriptorSetLayoutSsrVariant(); return mEngine.getPerViewDescriptorSetLayoutSsrVariant();
}
} }
// mDefinition.perViewDescriptorSetLayout{Vsm} is already resolved for MaterialDomain
if (useVsmDescriptorSetLayout) { if (useVsmDescriptorSetLayout) {
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
return mDefinition.perViewDescriptorSetLayoutVsm; return mDefinition.perViewDescriptorSetLayoutVsm;
} }
return mDefinition.perViewDescriptorSetLayout; return mDefinition.perViewDescriptorSetLayout;
@@ -280,14 +282,14 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
return FMaterialInstance::duplicate(mDefaultMaterialInstance, name); return FMaterialInstance::duplicate(mDefaultMaterialInstance, name);
} else { } else {
// but if we don't, just create an instance with all the default parameters // but if we don't, just create an instance with all the default parameters
return mEngine.createMaterialInstance(this, name); return mEngine.createMaterialInstance(this, name, UboBatchingMode::DEFAULT);
} }
} }
FMaterialInstance* FMaterial::getDefaultInstance() noexcept { FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) { if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
mDefaultMaterialInstance = mEngine.createMaterialInstance(this, mDefaultMaterialInstance =
mDefinition.name.c_str()); mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT);
mDefaultMaterialInstance->setDefaultInstance(true); mDefaultMaterialInstance->setDefaultInstance(true);
} }
return mDefaultMaterialInstance; return mDefaultMaterialInstance;
+1 -1
View File
@@ -108,7 +108,7 @@ public:
backend::CallbackHandler* handler, backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept; utils::Invocable<void(Material*)>&& callback) noexcept;
// Create an instance of this material // Creates an instance of this material, specifying the batching mode.
FMaterialInstance* createInstance(const char* name) const noexcept; FMaterialInstance* createInstance(const char* name) const noexcept;
bool hasParameter(const char* name) const noexcept; bool hasParameter(const char* name) const noexcept;
+42 -16
View File
@@ -58,15 +58,19 @@ using namespace utils;
namespace filament { namespace filament {
using namespace backend; using namespace backend;
using UboBatchingMode = FEngine::UboBatchingMode;
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) {
const char* name) noexcept if (batchingMode != UboBatchingMode::DEFAULT) {
: FMaterialInstance(engine, material, name, return batchingMode == UboBatchingMode::UBO_BATCHING;
engine.features.material.enable_material_instance_uniform_batching) { }
return engine.isUboBatchingEnabled() && domain == MaterialDomain::SURFACE;
} }
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
const char* name, bool useUboBatching) noexcept : mMaterial(material), UboBatchingMode batchingMode) noexcept
: mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()), mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mCulling(CullingMode::BACK), mCulling(CullingMode::BACK),
mShadowCulling(CullingMode::BACK), mShadowCulling(CullingMode::BACK),
@@ -76,10 +80,12 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
mHasScissor(false), mHasScissor(false),
mIsDoubleSided(false), mIsDoubleSided(false),
mIsDefaultInstance(false), mIsDefaultInstance(false),
mUseUboBatching(useUboBatching), mUseUboBatching(
shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())),
mTransparencyMode(TransparencyMode::DEFAULT), mTransparencyMode(TransparencyMode::DEFAULT),
mName(name ? CString(name) : material->getName()) { mName(name ? CString(name) : material->getName()) {
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
<< "UBO batching is not enabled.";
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
// even if the material doesn't have any parameters, we allocate a small UBO because it's // even if the material doesn't have any parameters, we allocate a small UBO because it's
@@ -153,7 +159,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mUseUboBatching(other->mUseUboBatching), mUseUboBatching(other->mUseUboBatching),
mScissorRect(other->mScissorRect), mScissorRect(other->mScissorRect),
mName(name ? CString(name) : other->mName) { mName(name ? CString(name) : other->mName) {
assert_invariant(!mUseUboBatching || engine.isUboBatchingEnabled());
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
FMaterial const* const material = other->getMaterial(); FMaterial const* const material = other->getMaterial();
@@ -193,8 +199,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
} }
} }
FMaterialInstance* FMaterialInstance::duplicate( FMaterialInstance* FMaterialInstance::duplicate(FMaterialInstance const* other,
FMaterialInstance const* other, const char* name) noexcept { const char* name) noexcept {
FMaterial const* const material = other->getMaterial(); FMaterial const* const material = other->getMaterial();
FEngine& engine = material->getEngine(); FEngine& engine = material->getEngine();
return engine.createMaterialInstance(material, other, name); return engine.createMaterialInstance(material, other, name);
@@ -238,15 +244,21 @@ void FMaterialInstance::commitStreamUniformAssociations(FEngine::DriverApi& driv
void FMaterialInstance::commit(FEngine& engine) const { void FMaterialInstance::commit(FEngine& engine) const {
if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) { if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) {
commit(engine.getDriverApi()); commit(engine.getDriverApi(), engine.getUboManager());
} }
} }
void FMaterialInstance::commit(FEngine::DriverApi& driver) const { void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const {
if (mUniforms.isDirty() || mHasStreamUniformAssociations) { if (mUniforms.isDirty() || mHasStreamUniformAssociations) {
mUniforms.clean(); mUniforms.clean();
if (mUseUboBatching) { if (mUseUboBatching) {
// TODO: update the content by `copyToMemoryMappedBuffer` assert_invariant(uboManager != nullptr);
if (!BufferAllocator::isValid(getAllocationId())) {
// The allocation hasn't happened yet, return.
return;
}
uboManager->updateSlot(driver, getAllocationId(), mUniforms.toBufferDescriptor(driver));
} }
else { else {
auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData); auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
@@ -271,6 +283,10 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver) const {
// TODO: eventually we should remove this in RELEASE builds // TODO: eventually we should remove this in RELEASE builds
fixMissingSamplers(); fixMissingSamplers();
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
// Commit descriptors if needed (e.g. when textures are updated,or the first time) // Commit descriptors if needed (e.g. when textures are updated,or the first time)
mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver); mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver);
} }
@@ -413,6 +429,13 @@ const char* FMaterialInstance::getName() const noexcept {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const { void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
if (!mDescriptorSet.getHandle()) {
return;
}
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
return;
}
if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) { if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) {
std::call_once(mMissingSamplersFlag, [this] { std::call_once(mMissingSamplersFlag, [this] {
@@ -439,7 +462,8 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
return; return;
} }
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL); mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL,
{ { mUboOffset }, driver });
} }
void FMaterialInstance::assignUboAllocation( void FMaterialInstance::assignUboAllocation(
@@ -449,8 +473,10 @@ void FMaterialInstance::assignUboAllocation(
assert_invariant(mUseUboBatching); assert_invariant(mUseUboBatching);
mUboData = id; mUboData = id;
mUboOffset = offset;
if (BufferAllocator::isValid(id)) { if (BufferAllocator::isValid(id)) {
mDescriptorSet.setBuffer(mMaterial->getDescriptorSetLayout(), 0, ubHandle, offset, // Use dynamic offset during binding, so the offset here is always zero.
mDescriptorSet.setBuffer(mMaterial->getDescriptorSetLayout(), 0, ubHandle, 0,
mUniforms.getSize()); mUniforms.getSize());
} }
} }
+5 -7
View File
@@ -56,11 +56,8 @@ class FTexture;
class FMaterialInstance : public MaterialInstance { class FMaterialInstance : public MaterialInstance {
public: public:
FMaterialInstance(FEngine& engine, FMaterial const* material, FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
const char* name) noexcept; FEngine::UboBatchingMode batchingMode) noexcept;
// Use this constructor when you need to override the ubo batching flag for an individual MI.
FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name, bool useUboBatching) noexcept;
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name); FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
FMaterialInstance(const FMaterialInstance& rhs) = delete; FMaterialInstance(const FMaterialInstance& rhs) = delete;
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete; FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
@@ -75,7 +72,7 @@ public:
void commit(FEngine& engine) const; void commit(FEngine& engine) const;
void commit(FEngine::DriverApi& driver) const; void commit(FEngine::DriverApi& driver, UboManager* uboManager) const;
void use(FEngine::DriverApi& driver, Variant variant = {}) const; void use(FEngine::DriverApi& driver, Variant variant = {}) const;
@@ -287,6 +284,7 @@ private:
}; };
std::variant<BufferAllocator::AllocationId, backend::Handle<backend::HwBufferObject>> mUboData; std::variant<BufferAllocator::AllocationId, backend::Handle<backend::HwBufferObject>> mUboData;
BufferAllocator::allocation_size_t mUboOffset = 0;
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters; tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
mutable DescriptorSet mDescriptorSet; mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms; UniformBuffer mUniforms;
@@ -308,7 +306,7 @@ private:
bool mHasScissor : 1; bool mHasScissor : 1;
bool mIsDoubleSided : 1; bool mIsDoubleSided : 1;
bool mIsDefaultInstance : 1; bool mIsDefaultInstance : 1;
bool mUseUboBatching : 1; const bool mUseUboBatching : 1;
TransparencyMode mTransparencyMode : 2; TransparencyMode mTransparencyMode : 2;
uint64_t mMaterialSortingKey = 0; uint64_t mMaterialSortingKey = 0;
+4
View File
@@ -452,6 +452,8 @@ void FRenderer::endFrame() {
mFrameInfoManager.endFrame(driver); mFrameInfoManager.endFrame(driver);
mFrameSkipper.submitFrame(driver); mFrameSkipper.submitFrame(driver);
engine.submitFrame();
driver.endFrame(mFrameId); driver.endFrame(mFrameId);
// gives the backend a chance to execute periodic tasks // gives the backend a chance to execute periodic tasks
@@ -587,6 +589,8 @@ void FRenderer::renderStandaloneView(FView const* view) {
// happen with Renderer::beginFrame/endFrame. // happen with Renderer::beginFrame/endFrame.
renderInternal(view, true); renderInternal(view, true);
engine.submitFrame();
driver.endFrame(mFrameId); driver.endFrame(mFrameId);
// engine.flush() has already been called by renderInternal(), but we need an extra one // engine.flush() has already been called by renderInternal(), but we need an extra one
+149 -139
View File
@@ -20,9 +20,11 @@
#include "details/BufferAllocator.h" #include "details/BufferAllocator.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <private/utils/Tracing.h>
#include <utils/Logger.h> #include <utils/Logger.h>
#include <vector>
namespace filament { namespace filament {
namespace { namespace {
@@ -33,6 +35,80 @@ using AllocationId = BufferAllocator::AllocationId;
using allocation_size_t = BufferAllocator::allocation_size_t; using allocation_size_t = BufferAllocator::allocation_size_t;
} // anonymous namespace } // anonymous namespace
// ------------------------------------------------------------------------------------------------
// FenceManager
// ------------------------------------------------------------------------------------------------
void UboManager::FenceManager::track(DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds) {
if (allocationIds.empty()) {
return;
}
mFenceAllocationList.emplace_back(driver.createFence(), std::move(allocationIds));
}
void UboManager::FenceManager::reclaimCompletedResources(DriverApi& driver,
std::function<void(AllocationId)> const& onReclaimed) {
uint32_t signaledCount = 0;
bool seenSignaledFence = false;
// Iterate from the newest fence to the oldest.
for (auto it = mFenceAllocationList.rbegin(); it != mFenceAllocationList.rend(); ++it) {
const Handle<HwFence>& fence = it->first;
const FenceStatus status = driver.getFenceStatus(fence);
// If we have already seen a signaled fence, we can assume all older fences
// are also complete, regardless of their reported status (e.g., TIMEOUT_EXPIRED).
// This is guaranteed by the in-order execution of GPU command queues.
if (seenSignaledFence) {
signaledCount++;
#ifndef NDEBUG
if (UTILS_UNLIKELY(status != FenceStatus::CONDITION_SATISFIED)) {
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;
}
if (status == FenceStatus::CONDITION_SATISFIED) {
seenSignaledFence = true;
signaledCount++;
}
}
if (signaledCount == 0) {
// No fences have completed, nothing to do.
return;
}
auto firstToKeep = mFenceAllocationList.begin() + signaledCount;
// Invoke the callback for all resources protected by completed fences.
for (auto it = mFenceAllocationList.begin(); it != firstToKeep; ++it) {
for (const AllocationId& id : it->second) {
onReclaimed(id);
}
// Destroy the fence handle as it's no longer needed.
driver.destroyFence(std::move(it->first));
}
mFenceAllocationList.erase(mFenceAllocationList.begin(), firstToKeep);
}
void UboManager::FenceManager::reset(DriverApi& driver) {
for (auto& [fence, _] : mFenceAllocationList) {
if (fence) {
driver.destroyFence(std::move(fence));
}
}
mFenceAllocationList.clear();
}
// ------------------------------------------------------------------------------------------------
// UboManager
// ------------------------------------------------------------------------------------------------
UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInBytes, UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInBytes,
allocation_size_t defaultTotalSizeInBytes) allocation_size_t defaultTotalSizeInBytes)
: mAllocator(defaultTotalSizeInBytes, defaultSlotSizeInBytes) { : mAllocator(defaultTotalSizeInBytes, defaultSlotSizeInBytes) {
@@ -41,17 +117,16 @@ UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInByt
void UboManager::beginFrame(DriverApi& driver, void UboManager::beginFrame(DriverApi& driver,
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& materialInstances) { const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& materialInstances) {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
// Check finished frames and decrement GPU count accordingly. // Check finished frames and decrement GPU count accordingly.
checkFenceAndUnlockSlots(driver); mFenceManager.reclaimCompletedResources(driver,
[this](AllocationId id) { mAllocator.releaseGpu(id); });
// Actually merge the slots. // Actually merge the slots.
mAllocator.releaseFreeSlots(); mAllocator.releaseFreeSlots();
// Traverse all MIs and see which of them need slot allocation. // Traverse all MIs and see which of them need slot allocation.
AllocationResult allocationResult = if (allocateOnDemand(materialInstances) == SUCCESS) {
updateMaterialInstanceAllocations(materialInstances, ON_DEMAND);
if (allocationResult == SUCCESS) {
// No need to grow the buffer, so we can just map the buffer for writing and return. // 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, mMemoryMappedBufferHandle = driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT,
"UboManager"); "UboManager");
@@ -64,24 +139,20 @@ void UboManager::beginFrame(DriverApi& driver,
reallocate(driver, requiredSize); reallocate(driver, requiredSize);
// Allocate slots for each MI on the new Ubo. // Allocate slots for each MI on the new Ubo.
UTILS_UNUSED_IN_RELEASE const AllocationResult needReallocationAgain = allocateAllInstances(materialInstances);
updateMaterialInstanceAllocations(materialInstances, ALWAYS);
assert_invariant(needReallocationAgain != REALLOCATION_REQUIRED);
// Map the buffer so that we can write to it // Map the buffer so that we can write to it
mMemoryMappedBufferHandle = mMemoryMappedBufferHandle =
driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT, "UboManager"); driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT, "UboManager");
// Migrate all MI data to the new allocated slots. // Invalidate the migrated MIs, so that next commit() call must be triggered.
for (const auto& materialInstance : materialInstances) { for (const auto& materialInstance : materialInstances) {
materialInstance.second.forEach([this, &driver](const FMaterialInstance* mi) { materialInstance.second.forEach([](const FMaterialInstance* mi) {
if (!mi->isUsingUboBatching()) { if (!mi->isUsingUboBatching()) {
return; return;
} }
const AllocationId allocationId = mi->getAllocationId(); mi->getUniformBuffer().invalidate();
assert_invariant(BufferAllocator::isValid(allocationId));
updateSlot(driver, allocationId, mi->getUniformBuffer().toBufferDescriptor(driver));
}); });
} }
} }
@@ -113,17 +184,11 @@ void UboManager::endFrame(DriverApi& driver,
}); });
} }
mFenceAllocationList.emplace_back( driver.createFence(), std::move(allocationIds) ); mFenceManager.track(driver, std::move(allocationIds));
} }
void UboManager::terminate(DriverApi& driver) { void UboManager::terminate(DriverApi& driver) {
for (auto& [fence, _]: mFenceAllocationList) { mFenceManager.reset(driver);
if (fence) {
driver.destroyFence(std::move(fence));
}
}
mFenceAllocationList.clear();
driver.destroyBufferObject(mUbHandle); driver.destroyBufferObject(mUbHandle);
} }
@@ -142,6 +207,66 @@ void UboManager::retireSlot(BufferAllocator::AllocationId id) {
mAllocator.retire(id); mAllocator.retire(id);
} }
UboManager::AllocationResult UboManager::allocateOnDemand(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances) {
// Collect all MIs that need allocation into two groups.
std::vector<FMaterialInstance*> newInstances;
std::vector<FMaterialInstance*> existingInstances;
for (const auto& [_, miList] : materialInstances) {
miList.forEach([&](FMaterialInstance* mi) {
if (!mi->isUsingUboBatching()) {
return;
}
if (BufferAllocator::isValid(mi->getAllocationId())) {
existingInstances.push_back(mi);
} else {
newInstances.push_back(mi);
}
});
}
bool reallocationNeeded = false;
// Pass 1: Allocate slots for new material instances (that don't have a slot yet).
for (FMaterialInstance* mi : newInstances) {
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
mi->assignUboAllocation(mUbHandle, newId, newOffset);
if (!BufferAllocator::isValid(newId)) {
reallocationNeeded = true;
}
}
// Pass 2: Allocate slots for existing material instances that need to be orphaned.
for (FMaterialInstance* mi : existingInstances) {
if (mi->getUniformBuffer().isDirty() && mAllocator.isLockedByGpu(mi->getAllocationId())) {
mAllocator.retire(mi->getAllocationId());
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
mi->assignUboAllocation(mUbHandle, newId, newOffset);
if (!BufferAllocator::isValid(newId)) {
reallocationNeeded = true;
}
}
}
return reallocationNeeded ? REALLOCATION_REQUIRED : SUCCESS;
}
void UboManager::allocateAllInstances(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances) {
for (const auto& [_, miList] : materialInstances) {
miList.forEach([this](FMaterialInstance* mi) {
if (!mi->isUsingUboBatching()) {
return;
}
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 { allocation_size_t UboManager::getTotalSize() const noexcept {
return mUboSize; return mUboSize;
} }
@@ -150,128 +275,13 @@ allocation_size_t UboManager::getAllocationOffset(AllocationId id) const {
return mAllocator.getAllocationOffset(id); return mAllocator.getAllocationOffset(id);
} }
void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
uint32_t signaledCount = 0;
bool seenSignaledFence = false;
// Iterate from the newest fence to the oldest.
for (auto it = mFenceAllocationList.rbegin(); it != mFenceAllocationList.rend(); ++it) {
const Handle<HwFence>& fence = it->first;
const FenceStatus status = driver.getFenceStatus(fence);
// If we have already seen a signaled fence, we can assume all older fences
// are also complete, regardless of their reported status (e.g., TIMEOUT_EXPIRED).
// This is guaranteed by the in-order execution of GPU command queues.
if (seenSignaledFence) {
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;
}
#endif
continue;
}
if (status == FenceStatus::CONDITION_SATISFIED) {
seenSignaledFence = true;
signaledCount++;
}
}
if (signaledCount == 0) {
// No fences have completed, nothing to do.
return;
}
auto firstToKeep = mFenceAllocationList.begin() + signaledCount;
// Release GPU locks for all completed fences.
for (auto it = mFenceAllocationList.begin(); it != firstToKeep; ++it) {
for (const AllocationId& id: it->second) {
mAllocator.releaseGpu(id);
}
// Destroy the fence handle as it's no longer needed.
driver.destroyFence(std::move(it->first));
}
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;
}
});
}
return result;
}
UboManager::AllocationResult UboManager::tryAllocateMaterialInstanceSlot(FMaterialInstance* mi,
BufferAllocator& allocator, const Handle<HwBufferObject>& ubHandle,
AllocationMode allocationMode) {
if (!mi->isUsingUboBatching()) {
return SUCCESS;
}
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;
}
// Even if the allocation is not valid, we need to set it to let the following
// process knows.
mi->assignUboAllocation(ubHandle, newId, newOffset);
if (!BufferAllocator::isValid(newId)) {
return REALLOCATION_REQUIRED;
}
return SUCCESS;
};
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);
}
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 SUCCESS;
}
void UboManager::reallocate(DriverApi& driver, allocation_size_t requiredSize) { void UboManager::reallocate(DriverApi& driver, allocation_size_t requiredSize) {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
if (mUbHandle) { if (mUbHandle) {
driver.destroyBufferObject(mUbHandle); driver.destroyBufferObject(mUbHandle);
} }
for (auto& [fence, allocations]: mFenceAllocationList) { mFenceManager.reset(driver);
driver.destroyFence(std::move(fence));
}
mFenceAllocationList.clear();
mAllocator.reset(requiredSize); mAllocator.reset(requiredSize);
mUboSize = requiredSize; mUboSize = requiredSize;
mUbHandle = driver.createBufferObject(requiredSize, BufferObjectBinding::UNIFORM, mUbHandle = driver.createBufferObject(requiredSize, BufferObjectBinding::UNIFORM,
+55 -21
View File
@@ -25,7 +25,9 @@
#include <backend/Handle.h> #include <backend/Handle.h>
#include <private/backend/DriverApi.h> #include <private/backend/DriverApi.h>
#include <functional>
#include <unordered_set> #include <unordered_set>
#include <vector>
namespace filament { namespace filament {
@@ -40,6 +42,47 @@ class FMaterialInstance;
// undefined behavior. // undefined behavior.
class UboManager { class UboManager {
public: public:
// This utility tracks resources that are in-use by the GPU across multiple frames.
// It uses backend fences to determine when the GPU has finished with a set of resources,
// allowing them to be safely reclaimed or reused.
//
// The typical usage is to `track()` a set of resources at the end of a frame and
// call `reclaimCompletedResources()` at the beginning of a future frame to free up
// resources from completed GPU work.
//
// This class is designed for single-threaded access.
class FenceManager {
public:
using AllocationId = BufferAllocator::AllocationId;
FenceManager() = default;
~FenceManager() = default;
FenceManager(FenceManager const&) = delete;
FenceManager(FenceManager&&) = delete;
// Creates a new fence to track a set of allocation IDs for the current frame.
// This marks the beginning of GPU's usage of these resources.
void track(backend::DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds);
// Checks all tracked fences and invokes a callback for resources associated with
// completed fences. This should be called once per frame.
void reclaimCompletedResources(backend::DriverApi& driver,
std::function<void(AllocationId)> const& onReclaimed);
// Destroys all tracked fences and clears the tracking list.
// This is used for cleanup during termination or major reallocations.
void reset(backend::DriverApi& driver);
private:
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame.
using FenceAndAllocations =
std::pair<backend::Handle<backend::HwFence>, std::unordered_set<AllocationId>>;
std::vector<FenceAndAllocations> mFenceAllocationList;
};
explicit UboManager(backend::DriverApi& driver, explicit UboManager(backend::DriverApi& driver,
BufferAllocator::allocation_size_t defaultSlotSizeInBytes, BufferAllocator::allocation_size_t defaultSlotSizeInBytes,
BufferAllocator::allocation_size_t defaultTotalSizeInBytes); BufferAllocator::allocation_size_t defaultTotalSizeInBytes);
@@ -82,30 +125,25 @@ public:
private: private:
constexpr static float BUFFER_SIZE_GROWTH_MULTIPLIER = 1.5f; constexpr static float BUFFER_SIZE_GROWTH_MULTIPLIER = 1.5f;
// Query the offset by the allocation id.
[[nodiscard]] BufferAllocator::allocation_size_t getAllocationOffset(
BufferAllocator::AllocationId id) const;
void checkFenceAndUnlockSlots(backend::DriverApi& driver);
enum AllocationResult { enum AllocationResult {
SUCCESS, SUCCESS,
REALLOCATION_REQUIRED REALLOCATION_REQUIRED
}; };
enum AllocationMode { // Query the offset by the allocation id.
ON_DEMAND, [[nodiscard]] BufferAllocator::allocation_size_t getAllocationOffset(
ALWAYS BufferAllocator::AllocationId id) const;
};
AllocationResult updateMaterialInstanceAllocations( AllocationResult allocateOnDemand(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances, materialInstances);
AllocationMode allocationMode);
static AllocationResult tryAllocateMaterialInstanceSlot(FMaterialInstance* mi, void allocateAllInstances(
BufferAllocator& allocator, const backend::Handle<backend::HwBufferObject>& ubHandle, const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
AllocationMode allocationMode); materialInstances);
void reallocate(backend::DriverApi& driver, BufferAllocator::allocation_size_t requiredSize); void reallocate(backend::DriverApi& driver, BufferAllocator::allocation_size_t requiredSize);
BufferAllocator::allocation_size_t calculateRequiredSize( BufferAllocator::allocation_size_t calculateRequiredSize(
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
materialInstances); materialInstances);
@@ -114,11 +152,7 @@ private:
backend::MemoryMappedBufferHandle mMemoryMappedBufferHandle; backend::MemoryMappedBufferHandle mMemoryMappedBufferHandle;
BufferAllocator::allocation_size_t mUboSize{}; BufferAllocator::allocation_size_t mUboSize{};
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame. FenceManager mFenceManager;
using FenceAllocationList = std::vector<std::pair<backend::Handle<backend::HwFence>,
std::unordered_set<BufferAllocator::AllocationId>>>;
FenceAllocationList mFenceAllocationList;
BufferAllocator mAllocator; BufferAllocator mAllocator;
}; };
+1 -1
View File
@@ -302,7 +302,7 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const Builder& builder)
if (!mBufferObjectsEnabled) { if (!mBufferObjectsEnabled) {
// If buffer objects are not enabled at the API level, then we create them internally. // If buffer objects are not enabled at the API level, then we create them internally.
#pragma nounroll #pragma nounroll
for (size_t index = 0; index < MAX_VERTEX_BUFFER_COUNT; ++index) { for (size_t index = 0; index < MAX_VERTEX_ATTRIBUTE_COUNT; ++index) {
size_t const i = mAttributes[index].buffer; size_t const i = mAttributes[index].buffer;
if (i != Attribute::BUFFER_UNUSED) { if (i != Attribute::BUFFER_UNUSED) {
assert_invariant(bufferSizes[i] > 0); assert_invariant(bufferSizes[i] > 0);
+4
View File
@@ -338,6 +338,10 @@ public:
return mDynamicResolution; return mDynamicResolution;
} }
math::float2 getLastDynamicResolutionScale() const noexcept {
return mScale;
}
void setRenderQuality(RenderQuality const& renderQuality) noexcept { void setRenderQuality(RenderQuality const& renderQuality) noexcept {
mRenderQuality = renderQuality; mRenderQuality = renderQuality;
} }
+1 -1
View File
@@ -39,7 +39,7 @@ void PostProcessDescriptorSet::init(FEngine& engine) noexcept {
// create the descriptor-set layout // create the descriptor-set layout
mDescriptorSetLayout = filament::DescriptorSetLayout{ mDescriptorSetLayout = filament::DescriptorSetLayout{
engine.getDescriptorSetLayoutFactory(), engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), descriptor_sets::getPostProcessLayout() }; engine.getDriverApi(), descriptor_sets::getDepthVariantLayout() };
// create the descriptor-set from the layout // create the descriptor-set from the layout
mDescriptorSet = DescriptorSet{ "PostProcessDescriptorSet", mDescriptorSetLayout }; mDescriptorSet = DescriptorSet{ "PostProcessDescriptorSet", mDescriptorSetLayout };
+2
View File
@@ -44,10 +44,12 @@ list(APPEND RESGEN_SOURCE ${DUMMY_SRC})
# away in Release builds # away in Release builds
if (TNT_DEV) if (TNT_DEV)
add_executable(test_${TARGET} add_executable(test_${TARGET}
MockDriver.h
filament_AtlasAllocator_test.cpp filament_AtlasAllocator_test.cpp
test_BufferAllocator.cpp test_BufferAllocator.cpp
test_BufferAllocatorStress.cpp test_BufferAllocatorStress.cpp
test_CircularQueue.cpp test_CircularQueue.cpp
test_FenceManager.cpp
filament_test_exposure.cpp filament_test_exposure.cpp
filament_rendering_test.cpp filament_rendering_test.cpp
filament_bimap_test.cpp filament_bimap_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
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec| Pod::Spec.new do |spec|
spec.name = "Filament" spec.name = "Filament"
spec.version = "1.67.1" spec.version = "1.67.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament" spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC." spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0" spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.1/filament-v1.67.1-ios.tgz" } spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.0/filament-v1.67.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = { spec.pod_target_xcconfig = {
@@ -28,9 +28,9 @@
namespace filament::descriptor_sets { namespace filament::descriptor_sets {
backend::DescriptorSetLayout const& getPostProcessLayout() noexcept;
backend::DescriptorSetLayout const& getDepthVariantLayout() noexcept; backend::DescriptorSetLayout const& getDepthVariantLayout() noexcept;
backend::DescriptorSetLayout const& getSsrVariantLayout() noexcept; backend::DescriptorSetLayout const& getSsrVariantLayout() noexcept;
backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept; backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept;
backend::DescriptorSetLayout getPerViewDescriptorSetLayout( backend::DescriptorSetLayout getPerViewDescriptorSetLayout(
+3 -16
View File
@@ -40,12 +40,7 @@ namespace filament::descriptor_sets {
using namespace backend; using namespace backend;
// used for post-processing passes // used to generate shadow-maps, structure and postfx passes
static constexpr std::initializer_list<DescriptorSetLayoutBinding> postProcessDescriptorSetLayoutList = {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
};
// used to generate shadow-maps
static constexpr std::initializer_list<DescriptorSetLayoutBinding> depthVariantDescriptorSetLayoutList = { static constexpr std::initializer_list<DescriptorSetLayoutBinding> depthVariantDescriptorSetLayoutList = {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
}; };
@@ -142,10 +137,6 @@ static const std::unordered_map<
{{ SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT }, DescriptorType::SAMPLER_EXTERNAL } {{ SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT }, DescriptorType::SAMPLER_EXTERNAL }
}; };
// used for post-processing passes
static DescriptorSetLayout const postProcessDescriptorSetLayout{ utils::StaticString("postProcess"),
postProcessDescriptorSetLayoutList };
// used to generate shadow-maps // used to generate shadow-maps
static DescriptorSetLayout const depthVariantDescriptorSetLayout{ static DescriptorSetLayout const depthVariantDescriptorSetLayout{
utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList
@@ -163,10 +154,6 @@ static DescriptorSetLayout perRenderableDescriptorSetLayout = {
utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList
}; };
DescriptorSetLayout const& getPostProcessLayout() noexcept {
return postProcessDescriptorSetLayout;
}
DescriptorSetLayout const& getDepthVariantLayout() noexcept { DescriptorSetLayout const& getDepthVariantLayout() noexcept {
return depthVariantDescriptorSetLayout; return depthVariantDescriptorSetLayout;
} }
@@ -280,10 +267,10 @@ DescriptorSetLayout getPerViewDescriptorSetLayout(
return layout; return layout;
} }
case MaterialDomain::POST_PROCESS: case MaterialDomain::POST_PROCESS:
return postProcessDescriptorSetLayout; return depthVariantDescriptorSetLayout;
case MaterialDomain::COMPUTE: case MaterialDomain::COMPUTE:
// TODO: what's the layout for compute? // TODO: what's the layout for compute?
return postProcessDescriptorSetLayout; return depthVariantDescriptorSetLayout;
} }
} }
+3 -3
View File
@@ -129,9 +129,9 @@ DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib
DescriptorSetLayout layout; DescriptorSetLayout layout;
layout.bindings.reserve(1 + samplers.size()); layout.bindings.reserve(1 + samplers.size());
layout.bindings.push_back(DescriptorSetLayoutBinding { DescriptorType::UNIFORM_BUFFER, layout.bindings.push_back(DescriptorSetLayoutBinding{ DescriptorType::UNIFORM_BUFFER,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::NONE, 0 }); +PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::DYNAMIC_OFFSET, 0 });
for (auto const& sampler: samplers) { for (auto const& sampler: samplers) {
DescriptorSetLayoutBinding layoutBinding{ DescriptorSetLayoutBinding layoutBinding{
@@ -225,7 +225,7 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER)); f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER));
f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)); f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT));
f.writeUint8(0); f.writeUint8(0);
f.writeUint8(uint8_t(DescriptorFlags::NONE)); f.writeUint8(uint8_t(DescriptorFlags::DYNAMIC_OFFSET));
f.writeUint16(0); f.writeUint16(0);
// all the material's sampler descriptors // all the material's sampler descriptors
+7
View File
@@ -13,6 +13,11 @@ set(PUBLIC_HDRS
) )
set(SRCS set(SRCS
src/DirIncluder.h
src/DirIncluder.cpp
src/IncludeCallback.h
src/Includes.h
src/Includes.cpp
src/JsonishLexeme.h src/JsonishLexeme.h
src/JsonishLexer.h src/JsonishLexer.h
src/JsonishParser.h src/JsonishParser.h
@@ -53,6 +58,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/ DESTINATION include)
project(test_matp) project(test_matp)
set(TARGET test_matp) set(TARGET test_matp)
set(SRCS set(SRCS
tests/test_includes.cpp
tests/test_includer.cpp
tests/test_matp.cpp tests/test_matp.cpp
tests/TestMaterialParser.h tests/TestMaterialParser.h
tests/test_compute_material.cpp tests/test_compute_material.cpp
@@ -155,6 +155,9 @@ public:
return mWorkarounds; return mWorkarounds;
} }
bool getInsertLineDirectives() const noexcept { return mInsertLineDirectives; }
bool getInsertLineDirectiveChecks() const noexcept { return mInsertLineDirectiveChecks; }
protected: protected:
bool mDebug = false; bool mDebug = false;
bool mIsValid = true; bool mIsValid = true;
@@ -174,6 +177,8 @@ protected:
filament::UserVariantFilterMask mVariantFilter = 0; filament::UserVariantFilterMask mVariantFilter = 0;
Workarounds mWorkarounds = Workarounds::ALL; Workarounds mWorkarounds = Workarounds::ALL;
bool mIncludeEssl1 = true; bool mIncludeEssl1 = true;
bool mInsertLineDirectives = true;
bool mInsertLineDirectiveChecks = true;
}; };
} // namespace matp } // namespace matp
@@ -20,9 +20,11 @@
#include <unordered_map> #include <unordered_map>
#include <utils/Status.h>
#include "Config.h" #include "Config.h"
#include <utils/Path.h>
#include <utils/Status.h>
namespace filamat { namespace filamat {
class MaterialBuilder; class MaterialBuilder;
} }
@@ -36,7 +38,13 @@ class MaterialLexeme;
class MaterialParser { class MaterialParser {
public: public:
MaterialParser(); MaterialParser();
// Resolves #include directives in the material by looking at the given file structure.
// Returns the string of resolved material with the state that indicates whether the include
// resolution was successful.
std::pair<utils::Status, utils::CString> resolveIncludes(
const std::unique_ptr<const char[]>& buffer, ssize_t size,
const utils::Path& materialFilePath,
bool insertLineDirectives, bool insertLineDirectiveCheck);
// Parses a string material so that it can be used in MaterialBuilder. // Parses a string material so that it can be used in MaterialBuilder.
// Call MaterialBuilder::init before passing in the builder; call MaterialBuilder::build to // Call MaterialBuilder::init before passing in the builder; call MaterialBuilder::build to
// create filamat::Package after. // create filamat::Package after.
@@ -20,7 +20,7 @@
#include <fstream> #include <fstream>
namespace matc { namespace matp {
bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& result) { bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& result) {
auto getHeaderPath = [&result, &includedBy, this]() { auto getHeaderPath = [&result, &includedBy, this]() {
@@ -66,5 +66,5 @@ bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& re
return true; return true;
} }
} // namespace matc } // namespace matp
@@ -21,7 +21,7 @@
#include <utils/Path.h> #include <utils/Path.h>
namespace matc { namespace matp {
// Functor callback handler used to resolve includes relative to a root include directory. // Functor callback handler used to resolve includes relative to a root include directory.
class DirIncluder { class DirIncluder {
@@ -38,6 +38,6 @@ private:
}; };
} // namespace matc } // namespace matp
#endif #endif
@@ -18,10 +18,11 @@
#define TNT_INCLUDER_H #define TNT_INCLUDER_H
#include <utils/CString.h> #include <utils/CString.h>
#include <utils/Status.h>
#include <functional> #include <functional>
namespace matc { namespace matp {
struct IncludeResult { struct IncludeResult {
// The include name of the root file, as if it were being included. // The include name of the root file, as if it were being included.
@@ -30,7 +31,7 @@ struct IncludeResult {
const utils::CString includeName; const utils::CString includeName;
// The following fields should be filled out by the IncludeCallback when processing an include, // The following fields should be filled out by the IncludeCallback when processing an include,
// or when calling resolveIncludes for the root file. // or when calling resolveIncludesRecursively for the root file.
// The full contents of the include file. This may contain additional, recursive include // The full contents of the include file. This may contain additional, recursive include
// directives. // directives.
@@ -58,12 +59,12 @@ struct IncludeResult {
* @param result is the IncludeResult that the callback should fill out. * @param result is the IncludeResult that the callback should fill out.
* @return true, if the include was resolved successfully, false otherwise. * @return true, if the include was resolved successfully, false otherwise.
* *
* For an example of implementing this callback, see tools/matc/src/matc/DirIncluder.h. * For an example of implementing this callback, see DirIncluder.h.
*/ */
using IncludeCallback = std::function<bool( using IncludeCallback = std::function<bool(
const utils::CString& includedBy, const utils::CString& includedBy,
IncludeResult& result)>; IncludeResult& result)>;
} // namespace matc } // namespace matp
#endif #endif
@@ -19,10 +19,11 @@
#include <utils/Log.h> #include <utils/Log.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/sstream.h> #include <utils/sstream.h>
#include <utils/Status.h>
#include <string> #include <string>
namespace matc { namespace matp {
static bool isWhitespace(char c) { static bool isWhitespace(char c) {
return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'); return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v');
@@ -75,12 +76,11 @@ static void findCommentRanges(const char (&beginToken)[BTSize], const char (&end
} }
bool resolveIncludes(IncludeResult& root, IncludeCallback callback, utils::Status resolveIncludesRecursively(IncludeResult& root, IncludeCallback callback,
const ResolveOptions& options, size_t depth) { const ResolveOptions& options, size_t depth) {
if (depth > 30) { if (depth > 30) {
// This is probably an include cycle. Stop here and report an error so we don't overflow. // This is probably an include cycle. Stop here and report an error so we don't overflow.
utils::slog.e << "Include depth > 30. Include cycle?" << utils::io::endl; return utils::Status::invalidArgument("Include depth > 30. Include cycle?");
return false;
} }
utils::CString& text = root.text; utils::CString& text = root.text;
@@ -88,7 +88,7 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
std::vector<FoundInclude> includes = parseForIncludes(text); std::vector<FoundInclude> includes = parseForIncludes(text);
if (includes.empty()) { if (includes.empty()) {
// No more to resolve. // No more to resolve.
return true; return utils::Status::ok();
} }
const size_t lineNumberOffset = includes[0].line; const size_t lineNumberOffset = includes[0].line;
@@ -196,28 +196,30 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
const auto include = includes[0]; const auto include = includes[0];
// Ask the includer to resolve this include. // Ask the includer to resolve this include.
if (!callback) { if (!callback) {
return false; return utils::Status::invalidArgument("No callback is provided");
} }
IncludeResult resolved { IncludeResult includeResult{
.includeName = include.name .includeName = include.name
}; };
if (!callback(root.name, resolved)) { if (!callback(root.name, includeResult)) {
utils::slog.e << "The included file \"" << include.name.c_str() utils::io::sstream errorMessage;
errorMessage << "The included file \"" << include.name.c_str()
<< "\" could not be found." << utils::io::endl; << "\" could not be found." << utils::io::endl;
return false; return utils::Status::invalidArgument(errorMessage.c_str());
} }
// Recursively resolve all of its includes. // Recursively resolve all of its includes.
if (!resolveIncludes(resolved, callback, options, depth + 1)) { if (utils::Status resolveStatus =
return false; resolveIncludesRecursively(includeResult, callback, options, depth + 1);
!resolveStatus.isOk()) {
return resolveStatus;
} }
text.replace(include.startPosition, include.length, resolved.text); text.replace(include.startPosition, include.length, includeResult.text);
includes = parseForIncludes(text); includes = parseForIncludes(text);
} }
return utils::Status::ok();
return true;
} }
std::vector<FoundInclude> parseForIncludes(const utils::CString& source) { std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
@@ -308,4 +310,4 @@ std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
return results; return results;
} }
} // namespace matc } // namespace matp
@@ -20,10 +20,11 @@
#include "IncludeCallback.h" #include "IncludeCallback.h"
#include <utils/CString.h> #include <utils/CString.h>
#include <utils/Status.h>
#include <vector> #include <vector>
namespace matc { namespace matp {
struct ResolveOptions { struct ResolveOptions {
// If true, insert #line directives before / after each include. // If true, insert #line directives before / after each include.
@@ -39,9 +40,10 @@ struct ResolveOptions {
}; };
// Recursively handle all the includes inside of root. // Recursively handle all the includes inside of root.
// Returns true if all includes were handled successfully, false otherwise. // Returns utils::Status::StatusCode::OK if all includes were handled successfully, otherwise an
// callback may be null, in which case any #include directives found will result in a failure. // error status with a failure message.
bool resolveIncludes(IncludeResult& root, IncludeCallback callback, // Callback may be null, in which case any #include directives found will result in a failure.
utils::Status resolveIncludesRecursively(IncludeResult& root, IncludeCallback callback,
const ResolveOptions& options, size_t depth = 0); const ResolveOptions& options, size_t depth = 0);
struct FoundInclude { struct FoundInclude {
@@ -53,6 +55,6 @@ struct FoundInclude {
std::vector<FoundInclude> parseForIncludes(const utils::CString& source); std::vector<FoundInclude> parseForIncludes(const utils::CString& source);
} // namespace matc } // namespace matp
#endif #endif
+31
View File
@@ -16,6 +16,8 @@
#include <filament-matp/MaterialParser.h> #include <filament-matp/MaterialParser.h>
#include "DirIncluder.h"
#include "Includes.h"
#include "JsonishLexer.h" #include "JsonishLexer.h"
#include "JsonishParser.h" #include "JsonishParser.h"
#include "MaterialLexeme.h" #include "MaterialLexeme.h"
@@ -393,6 +395,35 @@ utils::Status MaterialParser::processMaterialParameters(filamat::MaterialBuilder
return status; return status;
} }
std::pair<utils::Status, utils::CString> MaterialParser::resolveIncludes(
const std::unique_ptr<const char[]>& buffer, ssize_t size,
const utils::Path& materialFilePath,
bool insertLineDirectives, bool insertLineDirectiveCheck) {
ResolveOptions const options {
.insertLineDirectives = insertLineDirectives,
.insertLineDirectiveCheck = insertLineDirectiveCheck
};
// Inline material (i.e. resolve #include directives)
DirIncluder includer;
includer.setIncludeDirectory(materialFilePath.getParent());
utils::CString fileName = utils::CString(materialFilePath.getName().c_str());
// This is both the source and the result.
// This will be mutated by the below `resolveIncludesRecursively` call.
IncludeResult result {
.includeName = fileName,
.text = utils::CString(buffer.get(), size),
.name = CString("")
};
utils::Status resolveStatus =
matp::resolveIncludesRecursively(result, std::move(includer), options);
return { resolveStatus, result.text };
}
utils::Status MaterialParser::processTemplateSubstitutions( utils::Status MaterialParser::processTemplateSubstitutions(
const Config& config, ssize_t& size, std::unique_ptr<const char[]>& buffer) { const Config& config, ssize_t& size, std::unique_ptr<const char[]>& buffer) {
const auto& templateMap = config.getTemplateMap(); const auto& templateMap = config.getTemplateMap();
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include <matc/IncludeCallback.h> #include "IncludeCallback.h"
#include <utils/CString.h> #include <utils/CString.h>
@@ -37,7 +37,7 @@ public:
return *this; return *this;
} }
bool operator()(const utils::CString& includedBy, matc::IncludeResult& result) { bool operator()(const utils::CString& includedBy, matp::IncludeResult& result) {
auto key = result.includeName.c_str(); auto key = result.includeName.c_str();
auto found = mIncludeMap.find(key); auto found = mIncludeMap.find(key);
@@ -16,17 +16,17 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <matc/DirIncluder.h> #include "DirIncluder.h"
using namespace utils; using namespace utils;
using namespace matc; using namespace matp;
const utils::Path root = utils::Path(__FILE__).getParent(); const utils::Path root = utils::Path(__FILE__).getParent();
// TODO: these tests are disabled as they fail on CI, which needs investigation. // TODO: these tests are disabled as they fail on CI, which needs investigation.
TEST(DirIncluder, DISABLED_IncludeNonexistent) { TEST(DirIncluder, DISABLED_IncludeNonexistent) {
matc::DirIncluder includer; DirIncluder includer;
{ {
IncludeResult i { IncludeResult i {
.includeName = CString("nonexistent.h") .includeName = CString("nonexistent.h")
@@ -45,7 +45,7 @@ TEST(DirIncluder, DISABLED_IncludeNonexistent) {
} }
TEST(DirIncluder, DISABLED_IncludeFile) { TEST(DirIncluder, DISABLED_IncludeFile) {
matc::DirIncluder includer; DirIncluder includer;
includer.setIncludeDirectory(root); includer.setIncludeDirectory(root);
IncludeResult result { IncludeResult result {
@@ -63,7 +63,7 @@ TEST(DirIncluder, DISABLED_IncludeFile) {
} }
TEST(DirIncluder, DISABLED_IncludeFileFromIncluder) { TEST(DirIncluder, DISABLED_IncludeFileFromIncluder) {
matc::DirIncluder includer; DirIncluder includer;
includer.setIncludeDirectory(root); includer.setIncludeDirectory(root);
utils::CString includerFile((root + "Dir/Baz.h").c_str()); 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()); 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 <gtest/gtest.h>
#include "Includes.h"
#include "MockIncluder.h" #include "MockIncluder.h"
#include <matc/Includes.h>
#include <filamat/MaterialBuilder.h> #include <filamat/MaterialBuilder.h>
#include <utils/CString.h> #include <utils/CString.h>
@@ -27,7 +27,7 @@
#include <memory> #include <memory>
using namespace utils; using namespace utils;
using namespace matc; using namespace matp;
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
@@ -211,8 +211,8 @@ TEST(IncludeResolver, NoIncludes) {
IncludeResult source { IncludeResult source {
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, {}); utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ("no includes", source.text.c_str()); EXPECT_STREQ("no includes", source.text.c_str());
} }
@@ -224,8 +224,8 @@ TEST(IncludeResolver, SingleInclude) {
IncludeResult source { IncludeResult source {
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, {}); utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ("include", source.text.c_str()); EXPECT_STREQ("include", source.text.c_str());
} }
@@ -245,8 +245,8 @@ TEST(IncludeResolver, MultipleIncludes) {
IncludeResult source { IncludeResult source {
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, {}); utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ(utils::CString(R"( EXPECT_STREQ(utils::CString(R"(
1 1
2 2
@@ -270,8 +270,8 @@ TEST(IncludeResolver, IncludeWithinInclude) {
IncludeResult source { IncludeResult source {
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, {}); utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ(utils::CString(R"( EXPECT_STREQ(utils::CString(R"(
1 1
3 3
@@ -293,8 +293,8 @@ TEST(IncludeResolver, Includers) {
IncludeResult source { IncludeResult source {
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, {}); utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ(utils::CString(R"( EXPECT_STREQ(utils::CString(R"(
3 3
)").c_str(), source.text.c_str()); )").c_str(), source.text.c_str());
@@ -313,8 +313,8 @@ TEST(IncludeResolver, IncludeFailure) {
IncludeResult source { IncludeResult source {
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, {}); utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_FALSE(result); EXPECT_FALSE(resolved.isOk());
} }
{ {
utils::CString code(R"( utils::CString code(R"(
@@ -326,8 +326,8 @@ TEST(IncludeResolver, IncludeFailure) {
IncludeResult source { IncludeResult source {
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, {}); utils::Status resolved = resolveIncludesRecursively(source, includer, {});
EXPECT_FALSE(result); EXPECT_FALSE(resolved.isOk());
} }
} }
@@ -344,9 +344,9 @@ TEST(IncludeResolver, Cycle) {
IncludeResult source { IncludeResult source {
.text = code .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. // 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. // Helper function that lets us write comparison cases with line breaks.
@@ -395,8 +395,8 @@ TEST(IncludeResolver, SingleIncludeLineDirective) {
.includeName = utils::CString("root.h"), .includeName = utils::CString("root.h"),
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, options); utils::Status resolved = resolveIncludesRecursively(source, includer, options);
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ_TRIMMARGIN(R"( EXPECT_STREQ_TRIMMARGIN(R"(
| #line 1 "root.h" | #line 1 "root.h"
| #line 1 "test.h" | #line 1 "test.h"
@@ -421,8 +421,8 @@ TEST(IncludeResolver, MultipleIncludesLineDirective) {
.includeName = utils::CString("root.h"), .includeName = utils::CString("root.h"),
.text = code, .text = code,
}; };
bool result = resolveIncludes(source, includer, options); utils::Status resolved = resolveIncludesRecursively(source, includer, options);
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ_TRIMMARGIN(R"( EXPECT_STREQ_TRIMMARGIN(R"(
| #line 1 "root.h" | #line 1 "root.h"
@@ -453,8 +453,8 @@ TEST(IncludeResolver, MultipleIncludesSameLineLineDirective) {
.includeName = utils::CString("root.h"), .includeName = utils::CString("root.h"),
.text = code .text = code
}; };
bool result = resolveIncludes(source, includer, options); utils::Status resolved = resolveIncludesRecursively(source, includer, options);
EXPECT_TRUE(result); EXPECT_TRUE(resolved.isOk());
EXPECT_STREQ_TRIMMARGIN(R"( EXPECT_STREQ_TRIMMARGIN(R"(
| #line 1 "root.h" | #line 1 "root.h"
@@ -518,7 +518,8 @@ TEST_F(MaterialBuilder, Include) {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()), .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. // Set the material with the resolved material.
mBuilder.material(source.text.c_str()); mBuilder.material(source.text.c_str());
@@ -545,8 +546,8 @@ TEST_F(MaterialBuilder, IncludeVertex) {
IncludeResult source { IncludeResult source {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()), .text = utils::CString(shaderCode.c_str(), shaderCode.size()),
}; };
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
EXPECT_TRUE(resolveIncludes(source, std::move(includer), options)); EXPECT_TRUE(resolved.isOk());
// Set the materialVertex with the resolved material. // Set the materialVertex with the resolved material.
mBuilder.materialVertex(source.text.c_str()); mBuilder.materialVertex(source.text.c_str());
@@ -572,7 +573,8 @@ TEST_F(MaterialBuilder, IncludeWithinFunction) {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()), .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. // Set the material with the resolved material.
mBuilder.material(source.text.c_str()); mBuilder.material(source.text.c_str());
@@ -592,7 +594,8 @@ TEST_F(MaterialBuilder, IncludeFailure) {
.text = utils::CString(shaderCode.c_str(), shaderCode.size()), .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()); mBuilder.material(source.text.c_str());
+4 -4
View File
@@ -17,13 +17,13 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "TestMaterialParser.h" #include "TestMaterialParser.h"
#include <filamat/MaterialBuilder.h>
#include <filament-matp/MaterialParser.h>
#include "JsonishLexer.h" #include "JsonishLexer.h"
#include "JsonishParser.h" #include "JsonishParser.h"
#include "MaterialLexer.h" #include "MaterialLexer.h"
#include "MockIncluder.h"
#include <filamat/MaterialBuilder.h>
#include <filament-matp/MaterialParser.h>
class MaterialLexer: public ::testing::Test { class MaterialLexer: public ::testing::Test {
protected: protected:
+8
View File
@@ -37,6 +37,7 @@ set(SRCS
src/IcoSphere.cpp src/IcoSphere.cpp
src/MeshAssimp.cpp src/MeshAssimp.cpp
src/Sphere.cpp src/Sphere.cpp
src/VulkanPlatformHelper.cpp
) )
set(LIBS set(LIBS
@@ -67,18 +68,25 @@ set(MATERIAL_SRCS
if (APPLE) if (APPLE)
list(APPEND SRCS src/NativeWindowHelperCocoa.mm) list(APPEND SRCS src/NativeWindowHelperCocoa.mm)
list(APPEND SRCS src/VulkanPlatformHelperApple.mm)
list(APPEND LIBS "-framework Cocoa -framework QuartzCore") list(APPEND LIBS "-framework Cocoa -framework QuartzCore")
endif() endif()
if (LINUX) if (LINUX)
list(APPEND SRCS src/NativeWindowHelperLinux.cpp) list(APPEND SRCS src/NativeWindowHelperLinux.cpp)
list(APPEND SRCS src/VulkanPlatformHelperLinux.cpp)
endif() endif()
if (WIN32) if (WIN32)
list(APPEND SRCS src/NativeWindowHelperWindows.cpp) list(APPEND SRCS src/NativeWindowHelperWindows.cpp)
list(APPEND SRCS src/VulkanPlatformHelperWindows.cpp)
list(APPEND LIBS sdl2main) list(APPEND LIBS sdl2main)
endif() endif()
if (ANDROID)
list(APPEND SRCS src/VulkanPlatformHelperAndroid.cpp)
endif()
# ================================================================================================== # ==================================================================================================
# Compile resources # Compile resources
# ================================================================================================== # ==================================================================================================
@@ -0,0 +1,31 @@
/*
* 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_FILAMENTAPP_VULKAN_PLATFORM_HELPER_H
#define TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_H
namespace filament::backend {
class VulkanPlatform;
} // namespace filament::backend
namespace filament::filamentapp {
filament::backend::VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr);
void destroyVulkanPlatform(filament::backend::VulkanPlatform* platform);
} // namespace filament::filamentapp
#endif // TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_H
+7 -34
View File
@@ -40,12 +40,15 @@
#include <filament/SwapChain.h> #include <filament/SwapChain.h>
#include <filament/View.h> #include <filament/View.h>
#include <backend/Platform.h>
#ifndef NDEBUG #ifndef NDEBUG
#include <filament/DebugRegistry.h> #include <filament/DebugRegistry.h>
#endif #endif
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatform.h>
#include <filamentapp/VulkanPlatformHelper.h>
#endif #endif
#if defined(FILAMENT_SUPPORTS_WEBGPU) #if defined(FILAMENT_SUPPORTS_WEBGPU)
@@ -78,36 +81,6 @@ namespace {
using namespace filament::backend; using namespace filament::backend;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
class FilamentAppVulkanPlatform : public VulkanPlatform {
public:
FilamentAppVulkanPlatform(char const* gpuHintCstr) {
utils::CString gpuHint{ gpuHintCstr };
if (gpuHint.empty()) {
return;
}
VulkanPlatform::Customization::GPUPreference pref;
// Check to see if it is an integer, if so turn it into an index.
if (std::all_of(gpuHint.begin(), gpuHint.end(), ::isdigit)) {
char* p_end {};
pref.index = static_cast<int8_t>(std::strtol(gpuHint.c_str(), &p_end, 10));
} else {
pref.deviceName = gpuHint;
}
mCustomization = {
.gpu = pref
};
}
virtual VulkanPlatform::Customization getCustomization() const noexcept override {
return mCustomization;
}
private:
VulkanPlatform::Customization mCustomization;
};
#endif
#if defined(FILAMENT_SUPPORTS_WEBGPU) #if defined(FILAMENT_SUPPORTS_WEBGPU)
class FilamentAppWebGPUPlatform : public WebGPUPlatform { class FilamentAppWebGPUPlatform : public WebGPUPlatform {
public: public:
@@ -138,7 +111,7 @@ private:
}; };
#endif #endif
} // anonymous namespace }
FilamentApp& FilamentApp::get() { FilamentApp& FilamentApp::get() {
static FilamentApp filamentApp; static FilamentApp filamentApp;
@@ -611,7 +584,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
if (mVulkanPlatform) { if (mVulkanPlatform) {
delete mVulkanPlatform; filamentapp::destroyVulkanPlatform(mVulkanPlatform);
} }
#endif #endif
@@ -775,11 +748,11 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
engineConfig.stereoscopicType = Engine::StereoscopicType::NONE; engineConfig.stereoscopicType = Engine::StereoscopicType::NONE;
#endif #endif
Platform* platform = nullptr; backend::Platform* platform = nullptr;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
if (backend == Engine::Backend::VULKAN) { if (backend == Engine::Backend::VULKAN) {
platform = mFilamentApp->mVulkanPlatform = platform = mFilamentApp->mVulkanPlatform =
new FilamentAppVulkanPlatform(config.vulkanGPUHint.c_str()); filamentapp::createVulkanPlatform(config.vulkanGPUHint.c_str());
} }
#endif #endif
@@ -0,0 +1,50 @@
/*
* 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 <filamentapp/VulkanPlatformHelper.h>
#include <backend/platforms/VulkanPlatform.h>
#include <utils/CString.h>
#include <algorithm>
#include <cstdlib>
namespace filament::filamentapp {
using namespace filament::backend;
VulkanPlatform::Customization parseGpuHint(char const* gpuHintCstr) {
utils::CString gpuHint{ gpuHintCstr };
if (gpuHint.empty()) {
return {};
}
VulkanPlatform::Customization::GPUPreference pref;
// Check to see if it is an integer, if so turn it into an index.
if (std::all_of(gpuHint.begin(), gpuHint.end(), ::isdigit)) {
char* p_end{};
pref.index = static_cast<int8_t>(std::strtol(gpuHint.c_str(), &p_end, 10));
} else {
pref.deviceName = gpuHint;
}
return { .gpu = pref };
}
void destroyVulkanPlatform(VulkanPlatform* platform) {
delete platform;
}
} // namespace filament::filamentapp
@@ -0,0 +1,41 @@
/*
* 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/VulkanPlatformAndroid.h>
#include "VulkanPlatformHelperCommon.h"
namespace filament::filamentapp {
using namespace filament::backend;
class FilamentAppVulkanPlatform : public VulkanPlatformAndroid {
public:
FilamentAppVulkanPlatform(char const* gpuHintCstr) : mCustomization(parseGpuHint(gpuHintCstr)) {}
VulkanPlatform::Customization getCustomization() const noexcept override {
return mCustomization;
}
private:
VulkanPlatform::Customization mCustomization;
};
VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr) {
return new FilamentAppVulkanPlatform(gpuHintCstr);
}
} // namespace filament::filamentapp
@@ -0,0 +1,41 @@
/*
* 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/VulkanPlatformApple.h>
#include "VulkanPlatformHelperCommon.h"
namespace filament::filamentapp {
using namespace filament::backend;
class FilamentAppVulkanPlatform : public VulkanPlatformApple {
public:
FilamentAppVulkanPlatform(char const* gpuHintCstr) : mCustomization(parseGpuHint(gpuHintCstr)) {}
VulkanPlatform::Customization getCustomization() const noexcept override {
return mCustomization;
}
private:
VulkanPlatform::Customization mCustomization;
};
VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr) {
return new FilamentAppVulkanPlatform(gpuHintCstr);
}
} // namespace filament::filamentapp
@@ -0,0 +1,28 @@
/*
* 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_FILAMENTAPP_VULKAN_PLATFORM_HELPER_COMMON_H
#define TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_COMMON_H
#include <backend/platforms/VulkanPlatform.h>
namespace filament::filamentapp {
filament::backend::VulkanPlatform::Customization parseGpuHint(char const* gpuHintCstr);
} // namespace filament::filamentapp
#endif // TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_COMMON_H
@@ -0,0 +1,41 @@
/*
* 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/VulkanPlatformLinux.h>
#include "VulkanPlatformHelperCommon.h"
namespace filament::filamentapp {
using namespace filament::backend;
class FilamentAppVulkanPlatform : public VulkanPlatformLinux {
public:
FilamentAppVulkanPlatform(char const* gpuHintCstr) : mCustomization(parseGpuHint(gpuHintCstr)) {}
VulkanPlatform::Customization getCustomization() const noexcept override {
return mCustomization;
}
private:
VulkanPlatform::Customization mCustomization;
};
VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr) {
return new FilamentAppVulkanPlatform(gpuHintCstr);
}
} // namespace filament::filamentapp
@@ -0,0 +1,41 @@
/*
* 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 "VulkanPlatformHelperCommon.h"
namespace filament::filamentapp {
using namespace filament::backend;
class FilamentAppVulkanPlatform : public VulkanPlatformWindows {
public:
FilamentAppVulkanPlatform(char const* gpuHintCstr) : mCustomization(parseGpuHint(gpuHintCstr)) {}
VulkanPlatform::Customization getCustomization() const noexcept override {
return mCustomization;
}
private:
VulkanPlatform::Customization mCustomization;
};
VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr) {
return new FilamentAppVulkanPlatform(gpuHintCstr);
}
} // namespace filament::filamentapp
+11 -5
View File
@@ -38,7 +38,7 @@
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/JobSystem.h> #include <utils/JobSystem.h>
#include <utils/Log.h> #include <utils/Logger.h>
#include <utils/Path.h> #include <utils/Path.h>
#include <cgltf.h> #include <cgltf.h>
@@ -160,7 +160,7 @@ uint8_t const* parseDataUri(const char* uri, std::string* mimeType, size_t* psiz
inline void normalizeSkinningWeights(cgltf_data const* gltf) { inline void normalizeSkinningWeights(cgltf_data const* gltf) {
auto normalize = [](cgltf_accessor* data) { auto normalize = [](cgltf_accessor* data) {
if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) { if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) {
slog.w << "Cannot normalize weights, unsupported attribute type." << io::endl; LOG(WARNING) << "Cannot normalize weights, unsupported attribute type.";
return; return;
} }
uint8_t* bytes = (uint8_t*) data->buffer_view->buffer->data; uint8_t* bytes = (uint8_t*) data->buffer_view->buffer->data;
@@ -527,6 +527,12 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
const cgltf_texture& srcTexture = asset->mSourceAsset->hierarchy->textures[textureIndex]; const cgltf_texture& srcTexture = asset->mSourceAsset->hierarchy->textures[textureIndex];
const cgltf_image* image = srcTexture.basisu_image ? const cgltf_image* image = srcTexture.basisu_image ?
srcTexture.basisu_image : srcTexture.image; srcTexture.basisu_image : srcTexture.image;
if (!image) {
LOG(ERROR) << "Missing texture";
return {};
}
const cgltf_buffer_view* bv = image->buffer_view; const cgltf_buffer_view* bv = image->buffer_view;
const char* uri = image->uri; const char* uri = image->uri;
@@ -542,7 +548,7 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
auto foundProvider = mTextureProviders.find(mime); auto foundProvider = mTextureProviders.find(mime);
if (foundProvider == mTextureProviders.end()) { if (foundProvider == mTextureProviders.end()) {
slog.e << "Missing texture provider for " << mime << io::endl; LOG(ERROR) << "Missing texture provider for " << mime;
return {}; return {};
} }
TextureProvider* provider = foundProvider->second; TextureProvider* provider = foundProvider->second;
@@ -598,7 +604,7 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
} }
Path fullpath = Path(mGltfPath).getParent() + uri; Path fullpath = Path(mGltfPath).getParent() + uri;
if (!fullpath.exists()) { if (!fullpath.exists()) {
slog.e << "Unable to open " << fullpath << io::endl; LOG(ERROR) << "Unable to open " << fullpath;
return {}; return {};
} }
using namespace std; using namespace std;
@@ -620,7 +626,7 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
} }
const char* name = srcTexture.name ? srcTexture.name : uri; const char* name = srcTexture.name ? srcTexture.name : uri;
slog.e << "Unable to create texture " << name << ": " << provider->getPushMessage() << io::endl; LOG(ERROR) << "Unable to create texture " << name << ": " << provider->getPushMessage();
return {}; return {};
} }
+3 -3
View File
@@ -281,7 +281,7 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container)
} }
text << " " text << " "
<< setw(alignment) << fieldName.c_str() << setw(alignment) << fieldName.c_str_safe()
<< setw(shortAlignment) << toString(UniformType(fieldType)) << setw(shortAlignment) << toString(UniformType(fieldType))
<< arraySizeToString(fieldSize) << arraySizeToString(fieldSize)
<< setw(shortAlignment) << toString(Precision(fieldPrecision)) << setw(shortAlignment) << toString(Precision(fieldPrecision))
@@ -325,7 +325,7 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container)
} }
text << " " text << " "
<< setw(alignment) << fieldName.c_str() << setw(alignment) << fieldName.c_str_safe()
<< setw(shortAlignment) << +fieldBinding << setw(shortAlignment) << +fieldBinding
<< setw(shortAlignment) << toString(SamplerType(fieldType)) << setw(shortAlignment) << toString(SamplerType(fieldType))
<< setw(shortAlignment) << toString(Precision(fieldPrecision)) << setw(shortAlignment) << toString(Precision(fieldPrecision))
@@ -364,7 +364,7 @@ static bool printConstantInfo(ostream& text, const ChunkContainer& container) {
} }
text << " " text << " "
<< setw(alignment) << fieldName.c_str() << setw(alignment) << fieldName.c_str_safe()
<< setw(shortAlignment) << toString(ConstantType(fieldType)) << setw(shortAlignment) << toString(ConstantType(fieldType))
<< endl; << endl;
} }
-19
View File
@@ -12,17 +12,12 @@ set(TARGET matlang)
set(HDRS set(HDRS
src/matc/CommandlineConfig.h src/matc/CommandlineConfig.h
src/matc/Compiler.h src/matc/Compiler.h
src/matc/DirIncluder.h
src/matc/Includes.h
src/matc/IncludeCallback.h
src/matc/MaterialCompiler.h src/matc/MaterialCompiler.h
) )
set(SRCS set(SRCS
src/matc/Compiler.cpp src/matc/Compiler.cpp
src/matc/CommandlineConfig.cpp src/matc/CommandlineConfig.cpp
src/matc/DirIncluder.cpp
src/matc/Includes.cpp
src/matc/MaterialCompiler.cpp src/matc/MaterialCompiler.cpp
) )
@@ -68,17 +63,3 @@ set_target_properties(${TARGET} PROPERTIES FOLDER Tools)
# ================================================================================================== # ==================================================================================================
install(TARGETS ${TARGET} RUNTIME DESTINATION bin) install(TARGETS ${TARGET} RUNTIME DESTINATION bin)
# ==================================================================================================
# Tests
# ==================================================================================================
project(test_matc)
set(TARGET test_matc)
set(SRCS tests/test_includer.cpp tests/test_includes.cpp)
add_executable(${TARGET} ${SRCS})
target_link_libraries(${TARGET} matlang gtest)
target_include_directories(${TARGET} PRIVATE ${filamat_SOURCE_DIR}/src)
set_target_properties(test_matc PROPERTIES FOLDER Tests)
+23 -5
View File
@@ -68,6 +68,8 @@ static const option OPTIONS[] = {
{ "no-sampler-validation", no_argument, nullptr, 'F' }, { "no-sampler-validation", no_argument, nullptr, 'F' },
{ "save-raw-variants", no_argument, nullptr, 'R' }, { "save-raw-variants", no_argument, nullptr, 'R' },
{ "workarounds", required_argument, nullptr, 'W' }, { "workarounds", required_argument, nullptr, 'W' },
{ "no-insert-line-directives", no_argument, nullptr, 'n' },
{ "no-insert-line-directive-check", no_argument, nullptr, 'N' },
{ nullptr, 0, nullptr, 0 } // termination of the option list { nullptr, 0, nullptr, 0 } // termination of the option list
}; };
@@ -142,13 +144,26 @@ static void usage(char* name) {
" Reflect the specified metadata as JSON: parameters\n\n" " Reflect the specified metadata as JSON: parameters\n\n"
" --variant-filter=<filter>, -V <filter>\n" " --variant-filter=<filter>, -V <filter>\n"
" Filter out specified comma-separated variants:\n" " Filter out specified comma-separated variants:\n"
" directionalLighting, dynamicLighting, shadowReceiver, skinning, vsm, fog," " directionalLighting, dynamicLighting, shadowReceiver, skinning, vsm, fog,\n"
" ssr (screen-space reflections), stereo\n" " ssr (screen-space reflections), stereo\n"
" This variant filter is merged with the filter from the material, if any\n\n" " This variant filter is merged with the filter from the material, if any.\n\n"
" --workarounds, -W\n" " --workarounds, -W\n"
" Workarounds to apply: all or none. (default is none).\n\n" " Workarounds to apply: all or none. (default is none).\n\n"
" --version, -v\n" " --version, -v\n"
" Print the material version number\n\n" " Print the material version number\n\n"
" --output-format, -f\n"
" Specify output format: blob (default), header or mat.\n"
" When specifying the output as mat, it will return a mat\n"
" with #include directives resolved.\n\n"
" --no-insert-line-directives\n"
" By default insert #line directives before / after each #include.\n"
" Add this to omit line directives.\n\n"
" --no-insert-line-directive-check\n"
" By default it surrounds line directives with\n"
" #if defined(GL_GOOGLE_cpp_style_line_directive)\n"
" Some drivers may complain about the use of cpp style #line directives\n"
" if they don't support it.\n\n"
"Internal use and debugging only:\n" "Internal use and debugging only:\n"
" --optimize-none, -g, -O0\n" " --optimize-none, -g, -O0\n"
" Disable all shader optimizations, for debugging\n\n" " Disable all shader optimizations, for debugging\n\n"
@@ -156,9 +171,6 @@ static void usage(char* name) {
" Optimize shaders by running only the preprocessor\n\n" " Optimize shaders by running only the preprocessor\n\n"
" --raw, -w\n" " --raw, -w\n"
" Compile a raw GLSL shader into a SPIRV binary chunk\n\n" " Compile a raw GLSL shader into a SPIRV binary chunk\n\n"
" --output-format, -f\n"
" Specify output format: blob (default), header or mat. When specifying the "
" output as mat, it will return a mat with #include directives resolved.\n\n"
" --debug, -d\n" " --debug, -d\n"
" Generate extra data for debugging\n\n" " Generate extra data for debugging\n\n"
" --no-sampler-validation, -F\n" " --no-sampler-validation, -F\n"
@@ -388,6 +400,12 @@ bool CommandlineConfig::parse() {
case 'R': case 'R':
mSaveRawVariants = true; mSaveRawVariants = true;
break; break;
case 'n':
mInsertLineDirectives = false;
break;
case 'N':
mInsertLineDirectiveChecks = false;
break;
} }
} }
+9 -22
View File
@@ -16,9 +16,6 @@
#include "MaterialCompiler.h" #include "MaterialCompiler.h"
#include "DirIncluder.h"
#include "Includes.h"
#include <memory> #include <memory>
#include <iostream> #include <iostream>
#include <utility> #include <utility>
@@ -27,7 +24,6 @@
#include <filament-matp/Config.h> #include <filament-matp/Config.h>
#include <utils/CString.h>
#include <utils/JobSystem.h> #include <utils/JobSystem.h>
#include <utils/Log.h> #include <utils/Log.h>
#include <utils/Path.h> #include <utils/Path.h>
@@ -69,31 +65,22 @@ bool MaterialCompiler::run(const matp::Config& config) {
glslang::FinalizeProcess(); glslang::FinalizeProcess();
return success; return success;
} }
// Inline material (i.e. resolve #include directives) auto [resolvedStatus, resolvedString] =
DirIncluder includer; mParser.resolveIncludes(buffer, size, materialFilePath,
includer.setIncludeDirectory(materialFilePath.getParent()); config.getInsertLineDirectives(),
utils::CString fileName = utils::CString(materialFilePath.getName().c_str()); config.getInsertLineDirectiveChecks());
ResolveOptions const options { // It has failed to resolve the include directives.
.insertLineDirectives = true, if (!resolvedStatus.isOk()) {
.insertLineDirectiveCheck = true std::cerr << resolvedStatus.getMessage() << std::endl;
};
// This is both source and result, this will be mutated by `resolveIncludes` call.
IncludeResult result {
.includeName = fileName,
.text = utils::CString(buffer.get(), size),
.name = CString("")
};
if (!resolveIncludes(result, std::move(includer), options)) {
return false; return false;
} }
// Now that the buffer is mutated, we need to update the buffer pointer and the size // Now that the buffer is mutated, we need to update the buffer pointer and the size
// before parsing. // before parsing.
size = result.text.size(); size = resolvedString.size();
auto modifiedBuffer = std::make_unique<char[]>(size); auto modifiedBuffer = std::make_unique<char[]>(size);
std::strncpy(modifiedBuffer.get(), result.text.c_str(), size); std::strncpy(modifiedBuffer.get(), resolvedString.c_str(), size);
buffer = std::move(modifiedBuffer); buffer = std::move(modifiedBuffer);
if (config.getOutputFormat() == matp::Config::OutputFormat::MAT) { if (config.getOutputFormat() == matp::Config::OutputFormat::MAT) {
+1
View File
@@ -694,6 +694,7 @@ class_<View>("View")
.function("isStencilBufferEnabled", &View::isStencilBufferEnabled) .function("isStencilBufferEnabled", &View::isStencilBufferEnabled)
.function("setMaterialGlobal", &View::setMaterialGlobal) .function("setMaterialGlobal", &View::setMaterialGlobal)
.function("getMaterialGlobal", &View::getMaterialGlobal) .function("getMaterialGlobal", &View::getMaterialGlobal)
.function("getLastDynamicResolutionScale", &View::getLastDynamicResolutionScale)
.function("getFogEntity", &View::getFogEntity) .function("getFogEntity", &View::getFogEntity)
.function("clearFrameHistory", &View::clearFrameHistory); .function("clearFrameHistory", &View::clearFrameHistory);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "filament", "name": "filament",
"version": "1.67.1", "version": "1.67.0",
"description": "Real-time physically based rendering engine", "description": "Real-time physically based rendering engine",
"main": "filament.js", "main": "filament.js",
"module": "filament.js", "module": "filament.js",