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