Compare commits
78
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
107f25ca75 | ||
|
|
46806735e4 | ||
|
|
c410acb69f | ||
|
|
cb9eba3b3e | ||
|
|
dcf51ba20e | ||
|
|
73e6ccad96 | ||
|
|
ad5c409317 | ||
|
|
db597af272 | ||
|
|
4a46955bfb | ||
|
|
864bc3c1c1 | ||
|
|
2da395caf8 | ||
|
|
64eb27af8a | ||
|
|
98585bdc30 | ||
|
|
0803987df3 | ||
|
|
ffd32e9309 | ||
|
|
b587028aa5 | ||
|
|
43b9c06f79 | ||
|
|
05ffc76032 | ||
|
|
8e5ba51718 | ||
|
|
8aa9c4d004 | ||
|
|
d075a877f2 | ||
|
|
5dfd285105 | ||
|
|
51392c77aa | ||
|
|
e78691b12b | ||
|
|
bc65135c51 | ||
|
|
0108d66aa1 | ||
|
|
96443fa1fd | ||
|
|
61c4df9503 | ||
|
|
b990652702 | ||
|
|
7f7d4d89ea | ||
|
|
2b7f325cc7 | ||
|
|
b9768394a1 | ||
|
|
3e02d5016e | ||
|
|
483ef2f125 | ||
|
|
46dc9e7772 | ||
|
|
b8551e50e1 | ||
|
|
b64df46522 | ||
|
|
7f92dfd06c | ||
|
|
3304a0c8b6 | ||
|
|
0c2d23e55c | ||
|
|
59d1ed6a94 | ||
|
|
33c299d3ba | ||
|
|
1cc87aa9af | ||
|
|
eb1b30c7b1 | ||
|
|
ecedbad5ba | ||
|
|
d1998c1b67 | ||
|
|
c2177ab86a | ||
|
|
f4d87ad526 | ||
|
|
f2f9c547b0 | ||
|
|
ea1c5d8f24 | ||
|
|
42d39834df | ||
|
|
7ea7fa18eb | ||
|
|
a6b4c9fae1 | ||
|
|
8d02cd0383 | ||
|
|
016882336a | ||
|
|
3f150e03bf | ||
|
|
99308c98ba | ||
|
|
6eee4520c7 | ||
|
|
fa3a5d9eb4 | ||
|
|
17e4d2b92e | ||
|
|
13310e4ba0 | ||
|
|
eab8490d14 | ||
|
|
edece8f3dc | ||
|
|
2456299337 | ||
|
|
6e9afff7ba | ||
|
|
4425338559 | ||
|
|
a494f9ff6a | ||
|
|
ba680cf11a | ||
|
|
fc095413b3 | ||
|
|
d80526528c | ||
|
|
38e59fbd6b | ||
|
|
a5e6df1ad3 | ||
|
|
23d1329f37 | ||
|
|
25b37d36ad | ||
|
|
68265ae793 | ||
|
|
e8f3fc5a46 | ||
|
|
22db1be939 | ||
|
|
a3290d7656 |
@@ -822,6 +822,7 @@ if (IS_HOST_PLATFORM)
|
||||
endif()
|
||||
add_subdirectory(${TOOLS}/mipgen)
|
||||
add_subdirectory(${TOOLS}/normal-blending)
|
||||
add_subdirectory(${TOOLS}/rainbowgen)
|
||||
add_subdirectory(${TOOLS}/resgen)
|
||||
add_subdirectory(${TOOLS}/rgb-to-lmsr)
|
||||
add_subdirectory(${TOOLS}/roughness-prefilter)
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.55.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.56.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.55.1'
|
||||
pod 'Filament', '~> 1.56.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
+11
-1
@@ -7,6 +7,16 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.56.2
|
||||
|
||||
- vk: fix stage pool gc logic
|
||||
|
||||
## v1.56.1
|
||||
|
||||
## v1.56.0
|
||||
|
||||
- backend: descriptor layouts distinguish samplers and external samplers (b/376089915) [⚠️ **New Material Version**]
|
||||
|
||||
## v1.55.1
|
||||
|
||||
|
||||
@@ -654,7 +664,7 @@ Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
|
||||
- engine: Binary size improvements.
|
||||
- engine: Add basic support for instanced renderables [**NEW API**].
|
||||
- engine: Fix, first imaged passsed to `Stream::SetAcquiredImage` is ignored and leaked.
|
||||
- engine: Fix, first imaged passed to `Stream::SetAcquiredImage` is ignored and leaked.
|
||||
- Vulkan: Robustness improvements.
|
||||
- Java: Fix, lookAt z axis negated.
|
||||
- gltfio: Be graceful when model has > 4 weights per vert.
|
||||
|
||||
@@ -83,12 +83,12 @@ buildscript {
|
||||
'minSdk': 21,
|
||||
'targetSdk': 34,
|
||||
'compileSdk': 34,
|
||||
'kotlin': '2.0.0',
|
||||
'kotlin_coroutines': '1.9.0-RC',
|
||||
'buildTools': '34.0.0',
|
||||
'kotlin': '2.0.21',
|
||||
'kotlin_coroutines': '1.9.0',
|
||||
'buildTools': '35.0.0',
|
||||
'ndk': '27.0.11718014',
|
||||
'androidx_core': '1.13.1',
|
||||
'androidx_annotations': '1.8.0'
|
||||
'androidx_annotations': '1.9.0'
|
||||
]
|
||||
|
||||
ext.deps = [
|
||||
@@ -104,7 +104,7 @@ buildscript {
|
||||
]
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.4.1'
|
||||
classpath 'com.android.tools.build:gradle:8.6.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
|
||||
}
|
||||
|
||||
|
||||
@@ -499,6 +499,37 @@ Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass,
|
||||
return (jint)engine->getActiveFeatureLevel();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nHasFeatureFlag(JNIEnv *env, jclass clazz,
|
||||
jlong nativeEngine, jstring name_) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
std::optional<bool> result = engine->getFeatureFlag(name);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
return result.has_value();
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nSetFeatureFlag(JNIEnv *env, jclass clazz,
|
||||
jlong nativeEngine, jstring name_, jboolean value) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
jboolean result = engine->setFeatureFlag(name, (bool)value);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
return result;
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nGetFeatureFlag(JNIEnv *env, jclass clazz,
|
||||
jlong nativeEngine, jstring name_) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
std::optional<bool> result = engine->getFeatureFlag(name);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
return result.value_or(false); // we should never fail here
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nCreateBuilder(JNIEnv*,
|
||||
jclass) {
|
||||
Engine::Builder* builder = new Engine::Builder{};
|
||||
@@ -565,6 +596,16 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
builder->paused((bool) paused);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Engine_nSetBuilderFeature(JNIEnv *env, jclass clazz,
|
||||
jlong nativeBuilder, jstring name_, jboolean value) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
builder->feature(name, (bool)value);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
|
||||
@@ -222,6 +222,20 @@ Java_com_google_android_filament_View_nIsFrontFaceWindingInverted(JNIEnv*,
|
||||
return static_cast<jboolean>(view->isFrontFaceWindingInverted());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetTransparentPickingEnabled(JNIEnv*,
|
||||
jclass, jlong nativeView, jboolean enabled) {
|
||||
View* view = (View*) nativeView;
|
||||
view->setTransparentPickingEnabled(enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_View_nIsTransparentPickingEnabled(JNIEnv*,
|
||||
jclass, jlong nativeView) {
|
||||
View* view = (View*) nativeView;
|
||||
return static_cast<jboolean>(view->isTransparentPickingEnabled());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetAmbientOcclusion(JNIEnv*, jclass, jlong nativeView, jint ordinal) {
|
||||
View* view = (View*) nativeView;
|
||||
|
||||
@@ -258,6 +258,17 @@ public class Engine {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a feature flag value. This is the only way to set constant feature flags.
|
||||
* @param name feature name
|
||||
* @param value true to enable, false to disable
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
public Builder feature(@NonNull String name, boolean value) {
|
||||
nSetBuilderFeature(mNativeBuilder, name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of Engine
|
||||
*
|
||||
@@ -393,6 +404,7 @@ public class Engine {
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL backend.
|
||||
* @Deprecated use "backend.disable_parallel_shader_compile" feature flag instead
|
||||
*/
|
||||
public boolean disableParallelShaderCompile = false;
|
||||
|
||||
@@ -433,6 +445,7 @@ public class Engine {
|
||||
|
||||
/**
|
||||
* Disable backend handles use-after-free checks.
|
||||
* @Deprecated use "backend.disable_handle_use_after_free_check" feature flag instead
|
||||
*/
|
||||
public boolean disableHandleUseAfterFreeCheck = false;
|
||||
|
||||
@@ -469,6 +482,7 @@ public class Engine {
|
||||
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
|
||||
* This is only supported for:
|
||||
* - PlatformEGLAndroid
|
||||
* @Deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
|
||||
*/
|
||||
public boolean assertNativeWindowIsValid = false;
|
||||
}
|
||||
@@ -693,11 +707,11 @@ public class Engine {
|
||||
|
||||
/**
|
||||
* Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
|
||||
* eyes rendered is set at Engine creation time with the {@link
|
||||
* Engine#Config#stereoscopicEyeCount} setting.
|
||||
* eyes rendered is set at Engine creation time with the {@link Config#stereoscopicEyeCount}
|
||||
* setting.
|
||||
*
|
||||
* @return the max number of stereoscopic eyes supported
|
||||
* @see Engine#Config#stereoscopicEyeCount
|
||||
* @see Config#stereoscopicEyeCount
|
||||
*/
|
||||
public long getMaxStereoscopicEyes() {
|
||||
return nGetMaxStereoscopicEyes(getNativeObject());
|
||||
@@ -894,7 +908,8 @@ public class Engine {
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @param ma Material
|
||||
* @param mi MaterialInstance to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidMaterialInstance(@NonNull Material ma, MaterialInstance mi) {
|
||||
@@ -1316,6 +1331,39 @@ public class Engine {
|
||||
*/
|
||||
public static native long getSteadyClockTimeNano();
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a feature flag exists
|
||||
* @param name name of the feature flag to check
|
||||
* @return true if it exists false otherwise
|
||||
*/
|
||||
public boolean hasFeatureFlag(@NonNull String name) {
|
||||
return nHasFeatureFlag(mNativeObject, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a non-constant feature flag.
|
||||
* @param name name of the feature flag to set
|
||||
* @param value value to set
|
||||
* @return true if the value was set, false if the feature flag is constant or doesn't exist.
|
||||
*/
|
||||
public boolean setFeatureFlag(@NonNull String name, boolean value) {
|
||||
return nSetFeatureFlag(mNativeObject, name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of any feature flag.
|
||||
* @param name name of the feature flag
|
||||
* @return the value of the flag if it exists
|
||||
* @exception IllegalArgumentException is thrown if the feature flag doesn't exist
|
||||
*/
|
||||
public boolean getFeatureFlag(@NonNull String name) {
|
||||
if (!hasFeatureFlag(name)) {
|
||||
throw new IllegalArgumentException("The feature flag \"" + name + "\" doesn't exist");
|
||||
}
|
||||
return nGetFeatureFlag(mNativeObject, name);
|
||||
}
|
||||
|
||||
@UsedByReflection("TextureHelper.java")
|
||||
public long getNativeObject() {
|
||||
if (mNativeObject == 0) {
|
||||
@@ -1405,6 +1453,9 @@ public class Engine {
|
||||
private static native int nGetSupportedFeatureLevel(long nativeEngine);
|
||||
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
|
||||
private static native int nGetActiveFeatureLevel(long nativeEngine);
|
||||
private static native boolean nHasFeatureFlag(long nativeEngine, String name);
|
||||
private static native boolean nSetFeatureFlag(long nativeEngine, String name, boolean value);
|
||||
private static native boolean nGetFeatureFlag(long nativeEngine, String name);
|
||||
|
||||
private static native long nCreateBuilder();
|
||||
private static native void nDestroyBuilder(long nativeBuilder);
|
||||
@@ -1420,5 +1471,6 @@ public class Engine {
|
||||
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
|
||||
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
|
||||
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
|
||||
private static native void nSetBuilderFeature(long nativeBuilder, String name, boolean value);
|
||||
private static native long nBuilderBuild(long nativeBuilder);
|
||||
}
|
||||
|
||||
@@ -745,6 +745,33 @@ public class View {
|
||||
nSetFrontFaceWindingInverted(getNativeObject(), inverted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if transparent picking is enabled.
|
||||
*
|
||||
* @see #setTransparentPickingEnabled
|
||||
*/
|
||||
public boolean isTransparentPickingEnabled() {
|
||||
return nIsTransparentPickingEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables transparent picking. Disabled by default.
|
||||
*
|
||||
* When transparent picking is enabled, View::pick() will pick from both
|
||||
* transparent and opaque renderables. When disabled, View::pick() will only
|
||||
* pick from opaque renderables.
|
||||
*
|
||||
* <p>
|
||||
* Transparent picking will create an extra pass for rendering depth
|
||||
* from both transparent and opaque renderables.
|
||||
* </p>
|
||||
*
|
||||
* @param enabled true enables transparent picking, false disables it.
|
||||
*/
|
||||
public void setTransparentPickingEnabled(boolean enabled) {
|
||||
nSetTransparentPickingEnabled(getNativeObject(), enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets options relative to dynamic lighting for this view.
|
||||
*
|
||||
@@ -1281,6 +1308,8 @@ public class View {
|
||||
private static native boolean nIsPostProcessingEnabled(long nativeView);
|
||||
private static native void nSetFrontFaceWindingInverted(long nativeView, boolean inverted);
|
||||
private static native boolean nIsFrontFaceWindingInverted(long nativeView);
|
||||
private static native void nSetTransparentPickingEnabled(long nativeView, boolean enabled);
|
||||
private static native boolean nIsTransparentPickingEnabled(long nativeView);
|
||||
private static native void nSetAmbientOcclusion(long nativeView, int ordinal);
|
||||
private static native int nGetAmbientOcclusion(long nativeView);
|
||||
private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, float bilateralThreshold, int quality, int lowPassFilter, int upsampling, boolean enabled, boolean bentNormals, float minHorizonAngleRad);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.55.1
|
||||
VERSION_NAME=1.56.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Wed Nov 17 10:40:18 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -6,6 +6,10 @@ plugins {
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
|
||||
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
|
||||
@@ -13,7 +17,7 @@ filamentTools {
|
||||
}
|
||||
|
||||
// don't forget to update MainACtivity.kt when/if changing this.
|
||||
task copyMesh(type: Copy) {
|
||||
tasks.register('copyMesh', Copy) {
|
||||
from "../../../third_party/models/BusterDrone"
|
||||
into "src/main/assets/models"
|
||||
}
|
||||
|
||||
+10
-2
@@ -112,7 +112,13 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
private fun setupFilament() {
|
||||
engine = Engine.Builder().featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build()
|
||||
val config = Engine.Config()
|
||||
//config.forceGLES2Context = true
|
||||
|
||||
engine = Engine.Builder()
|
||||
.config(config)
|
||||
.featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0)
|
||||
.build()
|
||||
renderer = engine.createRenderer()
|
||||
scene = engine.createScene()
|
||||
view = engine.createView()
|
||||
@@ -123,7 +129,9 @@ class MainActivity : Activity() {
|
||||
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
|
||||
|
||||
// post-processing is not supported at feature level 0
|
||||
view.isPostProcessingEnabled = false
|
||||
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
|
||||
view.isPostProcessingEnabled = false
|
||||
}
|
||||
|
||||
// Tell the view which camera we want to use
|
||||
view.camera = camera
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -308,6 +308,11 @@ if (FILAMENT_ENABLE_MULTIVIEW)
|
||||
add_definitions(-DFILAMENT_ENABLE_MULTIVIEW)
|
||||
endif()
|
||||
|
||||
# Whether to force the profiling mode.
|
||||
if (FILAMENT_FORCE_PROFILING_MODE)
|
||||
add_definitions(-DFILAMENT_FORCE_PROFILING_MODE)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Definitions
|
||||
# ==================================================================================================
|
||||
@@ -607,6 +612,13 @@ else()
|
||||
-Wover-aligned
|
||||
-Werror
|
||||
)
|
||||
if (CMAKE_CXX_STANDARD EQUAL 20)
|
||||
# The lambdas for passes in PostProcessManager.cpp capture this
|
||||
# implicitly in a way that's deprecated in c++20, but can't easily be
|
||||
# fixed in a way that's backwards compatible with c++17:
|
||||
# https://www.nextptr.com/tutorial/ta1430524603/capture-this-in-lambda-expression-timeline-of-change
|
||||
list(APPEND FILAMENT_WARNINGS -Wno-deprecated-this-capture)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_compile_options(${TARGET} PRIVATE
|
||||
|
||||
@@ -179,11 +179,18 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/caching/VulkanDescriptorSetManager.h
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/memory/ResourceManager.cpp
|
||||
src/vulkan/memory/ResourceManager.h
|
||||
src/vulkan/memory/ResourcePointer.h
|
||||
src/vulkan/memory/Resource.cpp
|
||||
src/vulkan/memory/Resource.h
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
src/vulkan/spirv/VulkanSpirvUtils.cpp
|
||||
src/vulkan/spirv/VulkanSpirvUtils.h
|
||||
src/vulkan/VulkanAsyncHandles.cpp
|
||||
src/vulkan/VulkanAsyncHandles.h
|
||||
src/vulkan/VulkanBlitter.cpp
|
||||
src/vulkan/VulkanBlitter.h
|
||||
src/vulkan/VulkanBuffer.cpp
|
||||
@@ -214,9 +221,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/VulkanSwapChain.h
|
||||
src/vulkan/VulkanReadPixels.cpp
|
||||
src/vulkan/VulkanReadPixels.h
|
||||
src/vulkan/VulkanResourceAllocator.h
|
||||
src/vulkan/VulkanResources.cpp
|
||||
src/vulkan/VulkanResources.h
|
||||
src/vulkan/VulkanTexture.cpp
|
||||
src/vulkan/VulkanTexture.h
|
||||
src/vulkan/VulkanUtility.cpp
|
||||
@@ -371,10 +375,6 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS
|
||||
if (LINUX AND FILAMENT_SUPPORTS_OSMESA)
|
||||
set(OSMESA_COMPILE_FLAGS
|
||||
-I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
set(OSMESA_LINKER_FLAGS
|
||||
-Wl,-L${FILAMENT_OSMESA_PATH}/lib/x86_64-linux-gnu/
|
||||
-lOSMesa
|
||||
)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
@@ -388,6 +388,9 @@ else()
|
||||
-Wover-aligned
|
||||
-Werror
|
||||
)
|
||||
if (CMAKE_CXX_STANDARD EQUAL 20)
|
||||
list(APPEND FILAMENT_WARNINGS -Wno-deprecated-this-capture)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
@@ -227,6 +227,7 @@ enum class DescriptorType : uint8_t {
|
||||
SHADER_STORAGE_BUFFER,
|
||||
SAMPLER,
|
||||
INPUT_ATTACHMENT,
|
||||
SAMPLER_EXTERNAL
|
||||
};
|
||||
|
||||
enum class DescriptorFlags : uint8_t {
|
||||
@@ -318,8 +319,19 @@ struct Viewport {
|
||||
int32_t right() const noexcept { return left + int32_t(width); }
|
||||
//! get the top coordinate in window space of the viewport
|
||||
int32_t top() const noexcept { return bottom + int32_t(height); }
|
||||
};
|
||||
|
||||
friend bool operator==(Viewport const& lhs, Viewport const& rhs) noexcept {
|
||||
// clang can do this branchless with xor/or
|
||||
return lhs.left == rhs.left && lhs.bottom == rhs.bottom &&
|
||||
lhs.width == rhs.width && lhs.height == rhs.height;
|
||||
}
|
||||
|
||||
friend bool operator!=(Viewport const& lhs, Viewport const& rhs) noexcept {
|
||||
// clang is being dumb and uses branches
|
||||
return bool(((lhs.left ^ rhs.left) | (lhs.bottom ^ rhs.bottom)) |
|
||||
((lhs.width ^ rhs.width) | (lhs.height ^ rhs.height)));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies the mapping of the near and far clipping plane to window coordinates.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "bluegl/BlueGL.h"
|
||||
|
||||
#include "osmesa.h"
|
||||
#include <osmesa.h>
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -56,6 +56,7 @@ protected:
|
||||
|
||||
private:
|
||||
OSMesaContext mContext;
|
||||
void* mOsMesaApi = nullptr;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -88,6 +88,7 @@ public:
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
bool isProtected = false;
|
||||
};
|
||||
|
||||
struct ImageSyncData {
|
||||
@@ -199,6 +200,13 @@ public:
|
||||
*/
|
||||
virtual bool hasResized(SwapChainPtr handle);
|
||||
|
||||
/**
|
||||
* Check if the surface is protected.
|
||||
* @param handle The handle returned by createSwapChain()
|
||||
* @return Whether the swapchain is protected
|
||||
*/
|
||||
virtual bool isProtected(SwapChainPtr handle);
|
||||
|
||||
/**
|
||||
* Carry out a recreation of the swapchain.
|
||||
* @param handle The handle returned by createSwapChain()
|
||||
@@ -267,6 +275,23 @@ public:
|
||||
*/
|
||||
VkQueue getGraphicsQueue() const noexcept;
|
||||
|
||||
/**
|
||||
* @return The family index of the protected graphics queue selected for the
|
||||
* Vulkan backend.
|
||||
*/
|
||||
uint32_t getProtectedGraphicsQueueFamilyIndex() const noexcept;
|
||||
|
||||
/**
|
||||
* @return The index of the protected graphics queue (if there are multiple
|
||||
* graphics queues) selected for the Vulkan backend.
|
||||
*/
|
||||
uint32_t getProtectedGraphicsQueueIndex() const noexcept;
|
||||
|
||||
/**
|
||||
* @return The protected queue that was selected for the Vulkan backend.
|
||||
*/
|
||||
VkQueue getProtectedGraphicsQueue() const noexcept;
|
||||
|
||||
private:
|
||||
static ExtensionSet getSwapchainInstanceExtensions();
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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_SYSTRACEPROFILE_H
|
||||
#define TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#define PROFILE_SCOPE(marker) SYSTRACE_NAME(marker)
|
||||
|
||||
#define PROFILE_NAME_BEGINFRAME "backend::beginFrame"
|
||||
#define PROFILE_NAME_ENDFRAME "backend::endFrame"
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H
|
||||
|
||||
@@ -161,6 +161,7 @@ struct MetalContext {
|
||||
CullModeStateTracker cullModeState;
|
||||
WindingStateTracker windingState;
|
||||
DepthClampStateTracker depthClampState;
|
||||
ScissorRectStateTracker scissorRectState;
|
||||
Handle<HwRenderPrimitive> currentRenderPrimitive;
|
||||
|
||||
// State caches.
|
||||
|
||||
@@ -623,6 +623,8 @@ const char* toString(DescriptorType type) {
|
||||
return "SAMPLER";
|
||||
case DescriptorType::INPUT_ATTACHMENT:
|
||||
return "INPUT_ATTACHMENT";
|
||||
case DescriptorType::SAMPLER_EXTERNAL:
|
||||
return "SAMPLER_EXTERNAL";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,9 +797,20 @@ void MetalDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
}
|
||||
|
||||
void MetalDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
if (ph) {
|
||||
destruct_handle<MetalProgram>(ph);
|
||||
if (UTILS_UNLIKELY(!ph)) {
|
||||
return;
|
||||
}
|
||||
// Remove any cached pipeline states that refer to these programs.
|
||||
auto* metalProgram = handle_cast<MetalProgram>(ph);
|
||||
const auto& functions = metalProgram->getFunctionsIfPresent();
|
||||
if (UTILS_LIKELY(functions.isRaster())) { // only raster pipelines are cached
|
||||
const auto& raster = functions.getRasterFunctions();
|
||||
mContext->pipelineStateCache.removeIf([&](const MetalPipelineState& state) {
|
||||
const auto& [fragment, vertex] = raster;
|
||||
return state.fragmentFunction == fragment || state.vertexFunction == vertex;
|
||||
});
|
||||
}
|
||||
destruct_handle<MetalProgram>(ph);
|
||||
}
|
||||
|
||||
void MetalDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
@@ -819,17 +832,24 @@ void MetalDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
}
|
||||
|
||||
void MetalDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
||||
if (sch) {
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
// If the SwapChain is a pixel buffer, we need to wait for the current command buffer to
|
||||
// complete before destroying it. This is because pixel buffer SwapChains hold a
|
||||
// MetalExternalImage that could still being rendered into.
|
||||
if (UTILS_UNLIKELY(swapChain->isPixelBuffer())) {
|
||||
executeAfterCurrentCommandBufferCompletes(
|
||||
[this, sch]() mutable { destruct_handle<MetalSwapChain>(sch); });
|
||||
} else {
|
||||
destruct_handle<MetalSwapChain>(sch);
|
||||
}
|
||||
if (UTILS_UNLIKELY(!sch)) {
|
||||
return;
|
||||
}
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
if (mContext->currentDrawSwapChain == swapChain) {
|
||||
mContext->currentDrawSwapChain = nullptr;
|
||||
}
|
||||
if (mContext->currentReadSwapChain == swapChain) {
|
||||
mContext->currentReadSwapChain = nullptr;
|
||||
}
|
||||
// If the SwapChain is a pixel buffer, we need to wait for the current command buffer to
|
||||
// complete before destroying it. This is because pixel buffer SwapChains hold a
|
||||
// MetalExternalImage that could still being rendered into.
|
||||
if (UTILS_UNLIKELY(swapChain->isPixelBuffer())) {
|
||||
executeAfterCurrentCommandBufferCompletes(
|
||||
[this, sch]() mutable { destruct_handle<MetalSwapChain>(sch); });
|
||||
} else {
|
||||
destruct_handle<MetalSwapChain>(sch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,10 +872,20 @@ void MetalDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh)
|
||||
|
||||
void MetalDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
||||
DEBUG_LOG("destroyDescriptorSet(dsh = %d)\n", dsh.getId());
|
||||
if (dsh) {
|
||||
executeAfterCurrentCommandBufferCompletes(
|
||||
[this, dsh]() mutable { destruct_handle<MetalDescriptorSet>(dsh); });
|
||||
if (!dsh) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unbind this descriptor set.
|
||||
auto* descriptorSet = handle_cast<MetalDescriptorSet>(dsh);
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
if (UTILS_UNLIKELY(mContext->currentDescriptorSets[i] == descriptorSet)) {
|
||||
mContext->currentDescriptorSets[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
executeAfterCurrentCommandBufferCompletes(
|
||||
[this, dsh]() mutable { destruct_handle<MetalDescriptorSet>(dsh); });
|
||||
}
|
||||
|
||||
void MetalDriver::terminate() {
|
||||
@@ -1227,6 +1257,7 @@ void MetalDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
mContext->depthStencilState.invalidate();
|
||||
mContext->cullModeState.invalidate();
|
||||
mContext->windingState.invalidate();
|
||||
mContext->scissorRectState.invalidate();
|
||||
mContext->currentPolygonOffset = {0.0f, 0.0f};
|
||||
|
||||
mContext->finalizedDescriptorSets.clear();
|
||||
@@ -1793,6 +1824,16 @@ void MetalDriver::bindDescriptorSet(
|
||||
backend::DescriptorSetHandle dsh,
|
||||
backend::descriptor_set_t set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
|
||||
if (UTILS_UNLIKELY(!dsh)) {
|
||||
DEBUG_LOG("bindDescriptorSet(dsh = null, set = %d, offsets = [])\n", set);
|
||||
mContext->currentDescriptorSets[set] = nullptr;
|
||||
mContext->vertexDescriptorBindings.setBuffer(nil, 0, set);
|
||||
mContext->fragmentDescriptorBindings.setBuffer(nil, 0, set);
|
||||
mContext->dynamicOffsets.setOffsets(set, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto descriptorSet = handle_cast<MetalDescriptorSet>(dsh);
|
||||
const size_t dynamicBindings = descriptorSet->layout->getDynamicOffsetCount();
|
||||
utils::FixedCapacityVector<size_t> offsetsVector(dynamicBindings, 0);
|
||||
@@ -1953,7 +1994,11 @@ void MetalDriver::scissor(Viewport scissorBox) {
|
||||
.height = static_cast<NSUInteger>(bottom - top)
|
||||
};
|
||||
|
||||
[mContext->currentRenderPassEncoder setScissorRect:scissorRect];
|
||||
auto& srs = mContext->scissorRectState;
|
||||
srs.updateState(scissorRect);
|
||||
if (srs.stateChanged()) {
|
||||
[mContext->currentRenderPassEncoder setScissorRect:scissorRect];
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
|
||||
@@ -71,7 +71,8 @@ constexpr inline MTLIndexType getIndexType(size_t elementSize) noexcept {
|
||||
} else if (elementSize == 4) {
|
||||
return MTLIndexTypeUInt32;
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Index element size not supported.";
|
||||
assert_invariant(false);
|
||||
return MTLIndexTypeUInt16;
|
||||
}
|
||||
|
||||
constexpr inline MTLVertexFormat getMetalFormat(ElementType type, bool normalized) noexcept {
|
||||
|
||||
@@ -202,6 +202,7 @@ public:
|
||||
MetalProgram(MetalContext& context, Program&& program) noexcept;
|
||||
|
||||
const MetalShaderCompiler::MetalFunctionBundle& getFunctions();
|
||||
const MetalShaderCompiler::MetalFunctionBundle& getFunctionsIfPresent() const;
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
@@ -437,7 +438,7 @@ struct MetalTimerQuery : public HwTimerQuery {
|
||||
|
||||
struct Status {
|
||||
std::atomic<bool> available {false};
|
||||
uint64_t elapsed {0}; // only valid if available is true
|
||||
std::atomic<uint64_t> elapsed {0}; // only valid if available is true
|
||||
};
|
||||
|
||||
std::shared_ptr<Status> status;
|
||||
|
||||
@@ -495,6 +495,10 @@ const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctions() {
|
||||
return mFunctionBundle;
|
||||
}
|
||||
|
||||
const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctionsIfPresent() const {
|
||||
return mFunctionBundle;
|
||||
}
|
||||
|
||||
void MetalProgram::initialize() {
|
||||
if (!mToken) {
|
||||
return;
|
||||
@@ -1290,7 +1294,8 @@ id<MTLArgumentEncoder> MetalDescriptorSetLayout::getArgumentEncoderSlow(id<MTLDe
|
||||
[arguments addObject:bufferArgument];
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SAMPLER: {
|
||||
case DescriptorType::SAMPLER:
|
||||
case DescriptorType::SAMPLER_EXTERNAL: {
|
||||
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
|
||||
textureArgument.index = binding.binding * 2;
|
||||
textureArgument.dataType = MTLDataTypeTexture;
|
||||
@@ -1405,7 +1410,8 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
||||
atIndex:binding.binding * 2];
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SAMPLER: {
|
||||
case DescriptorType::SAMPLER:
|
||||
case DescriptorType::SAMPLER_EXTERNAL: {
|
||||
auto found = textures.find(binding.binding);
|
||||
if (found == textures.end()) {
|
||||
[encoder setTexture:driver->mContext->emptyTexture atIndex:binding.binding * 2];
|
||||
|
||||
@@ -83,6 +83,10 @@ public:
|
||||
return std::get<Compute>(mPrograms);
|
||||
}
|
||||
|
||||
bool isRaster() const { return std::holds_alternative<Raster>(mPrograms); }
|
||||
|
||||
bool isCompute() const { return std::holds_alternative<Compute>(mPrograms); }
|
||||
|
||||
static MetalFunctionBundle none() {
|
||||
return MetalFunctionBundle(None{});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
|
||||
#include <memory>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Invocable.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
@@ -160,6 +162,8 @@ template<typename StateType,
|
||||
typename HashFn = utils::hash::MurmurHashFn<StateType>>
|
||||
class StateCache {
|
||||
|
||||
using MapType = tsl::robin_map<StateType, MetalType, HashFn>;
|
||||
|
||||
public:
|
||||
|
||||
StateCache() = default;
|
||||
@@ -169,6 +173,18 @@ public:
|
||||
|
||||
void setDevice(id<MTLDevice> device) noexcept { mDevice = device; }
|
||||
|
||||
void removeIf(utils::Invocable<bool(const StateType&)> fn) noexcept {
|
||||
typename MapType::const_iterator it = mStateCache.begin();
|
||||
while (it != mStateCache.end()) {
|
||||
const auto& [key, _] = *it;
|
||||
if (UTILS_UNLIKELY(fn(key))) {
|
||||
it = mStateCache.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetalType getOrCreateState(const StateType& state) noexcept {
|
||||
assert_invariant(mDevice);
|
||||
|
||||
@@ -196,7 +212,7 @@ private:
|
||||
StateCreator creator;
|
||||
id<MTLDevice> mDevice = nil;
|
||||
|
||||
tsl::robin_map<StateType, MetalType, HashFn> mStateCache;
|
||||
MapType mStateCache;
|
||||
|
||||
};
|
||||
|
||||
@@ -204,9 +220,8 @@ private:
|
||||
// Different kinds of state, like pipeline state, uniform buffer state, etc., are passed to the
|
||||
// current Metal command encoder and persist throughout the lifetime of the encoder (a frame).
|
||||
// StateTracker is used to prevent calling redundant state change methods.
|
||||
template<typename StateType>
|
||||
template <typename StateType, typename StateEqual = std::equal_to<StateType>>
|
||||
class StateTracker {
|
||||
|
||||
public:
|
||||
|
||||
// Call to force the state to dirty at the beginning of each frame, as all state must be
|
||||
@@ -214,7 +229,7 @@ public:
|
||||
void invalidate() noexcept { mStateDirty = true; }
|
||||
|
||||
void updateState(const StateType& newState) noexcept {
|
||||
if (mCurrentState != newState) {
|
||||
if (!StateEqual()(mCurrentState, newState)) {
|
||||
mCurrentState = newState;
|
||||
mStateDirty = true;
|
||||
}
|
||||
@@ -235,7 +250,6 @@ private:
|
||||
|
||||
bool mStateDirty = true;
|
||||
StateType mCurrentState = {};
|
||||
|
||||
};
|
||||
|
||||
// Pipeline state
|
||||
@@ -343,6 +357,16 @@ using DepthStencilStateTracker = StateTracker<DepthStencilState>;
|
||||
using DepthStencilStateCache = StateCache<DepthStencilState, id<MTLDepthStencilState>,
|
||||
DepthStateCreator>;
|
||||
|
||||
struct MtlScissorRectEqual {
|
||||
bool operator()(const MTLScissorRect& lhs, const MTLScissorRect& rhs) const {
|
||||
return lhs.height == rhs.height &&
|
||||
lhs.width == rhs.width &&
|
||||
lhs.x == rhs.x &&
|
||||
lhs.y == rhs.y;
|
||||
}
|
||||
};
|
||||
using ScissorRectStateTracker = StateTracker<MTLScissorRect, MtlScissorRectEqual>;
|
||||
|
||||
// Uniform buffers
|
||||
|
||||
class MetalBufferObject;
|
||||
|
||||
@@ -69,7 +69,9 @@ public:
|
||||
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
|
||||
assert_invariant(binding < MAX_DESCRIPTOR_COUNT);
|
||||
assert_invariant(entry.binding < 128); // we reserve 1 bit for the type right now
|
||||
mStorage[set][binding] = { (uint8_t)entry.binding, entry.type == DescriptorType::SAMPLER };
|
||||
mStorage[set][binding] = { (uint8_t)entry.binding,
|
||||
entry.type == DescriptorType::SAMPLER ||
|
||||
entry.type == DescriptorType::SAMPLER_EXTERNAL };
|
||||
mActiveDescriptors[set].set(binding);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle ds
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SAMPLER:
|
||||
case DescriptorType::SAMPLER_EXTERNAL:
|
||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||
desc.emplace<SamplerGLES2>();
|
||||
} else {
|
||||
|
||||
@@ -614,7 +614,7 @@ FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
||||
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
|
||||
gets.max_combined_texture_image_units >=
|
||||
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
||||
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
||||
}
|
||||
}
|
||||
@@ -623,15 +623,13 @@ FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
|
||||
# ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
else if (UTILS_UNLIKELY(major == 2)) {
|
||||
// Runtime OpenGL version is ES 2.x
|
||||
# if defined(BACKEND_OPENGL_LEVEL_GLES30)
|
||||
// mandatory extensions (all supported by Mali-400 and Adreno 304)
|
||||
assert_invariant(exts.OES_depth_texture);
|
||||
assert_invariant(exts.OES_depth24);
|
||||
assert_invariant(exts.OES_packed_depth_stencil);
|
||||
assert_invariant(exts.OES_rgb8_rgba8);
|
||||
assert_invariant(exts.OES_standard_derivatives);
|
||||
assert_invariant(exts.OES_texture_npot);
|
||||
# endif
|
||||
// note: mandatory extensions (all supported by Mali-400 and Adreno 304)
|
||||
// OES_depth_texture
|
||||
// OES_depth24
|
||||
// OES_packed_depth_stencil
|
||||
// OES_rgb8_rgba8
|
||||
// OES_standard_derivatives
|
||||
// OES_texture_npot
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
|
||||
}
|
||||
# endif // IOS
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "OpenGLDriverFactory.h"
|
||||
#include "OpenGLProgram.h"
|
||||
#include "OpenGLTimerQuery.h"
|
||||
#include "SystraceProfile.h"
|
||||
#include "gl_headers.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
@@ -91,24 +92,47 @@
|
||||
#define DEBUG_GROUP_MARKER_NONE 0x00 // no debug marker
|
||||
#define DEBUG_GROUP_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
|
||||
#define DEBUG_GROUP_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
|
||||
#define DEBUG_GROUP_MARKER_ALL 0x03 // all markers
|
||||
#define DEBUG_GROUP_MARKER_ALL 0xFF // all markers
|
||||
|
||||
#define DEBUG_MARKER_NONE 0x00 // no debug marker
|
||||
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
|
||||
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
|
||||
#define DEBUG_MARKER_ALL 0x03 // all markers
|
||||
#define DEBUG_MARKER_PROFILE 0x04 // profiling on the backend side (systrace)
|
||||
#define DEBUG_MARKER_ALL (0xFF & ~DEBUG_MARKER_PROFILE) // all markers
|
||||
|
||||
// set to the desired debug marker level (for user markers [default: All])
|
||||
#define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_ALL
|
||||
|
||||
// set to the desired debug level (for internal debugging [Default: None])
|
||||
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
|
||||
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
|
||||
|
||||
#if DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
|
||||
# define DEBUG_MARKER() \
|
||||
DebugMarker _debug_marker(*this, __func__);
|
||||
// Override the debug markers if we are forcing profiling mode
|
||||
#if defined(FILAMENT_FORCE_PROFILING_MODE)
|
||||
# undef DEBUG_GROUP_MARKER_LEVEL
|
||||
# undef DEBUG_MARKER_LEVEL
|
||||
|
||||
# define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_NONE
|
||||
# define DEBUG_MARKER_LEVEL DEBUG_MARKER_PROFILE
|
||||
#endif
|
||||
|
||||
#if DEBUG_MARKER_LEVEL == DEBUG_MARKER_PROFILE
|
||||
# define DEBUG_MARKER()
|
||||
# define PROFILE_MARKER(marker) PROFILE_SCOPE(marker);
|
||||
# if DEBUG_GROUP_MARKER_LEVEL != DEBUG_GROUP_MARKER_NONE
|
||||
# error PROFILING is exclusive; group markers must be disabled.
|
||||
# endif
|
||||
# ifndef NDEBUG
|
||||
# error PROFILING is meaningless in DEBUG mode.
|
||||
# endif
|
||||
#elif DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
|
||||
# define DEBUG_MARKER() DebugMarker _debug_marker(*this, __func__);
|
||||
# define PROFILE_MARKER(marker) DEBUG_MARKER()
|
||||
# if DEBUG_MARKER_LEVEL & DEBUG_MARKER_PROFILE
|
||||
# error PROFILING is exclusive; all other debug features must be disabled.
|
||||
# endif
|
||||
#else
|
||||
# define DEBUG_MARKER()
|
||||
# define PROFILE_MARKER(marker)
|
||||
#endif
|
||||
|
||||
using namespace filament::math;
|
||||
@@ -382,24 +406,21 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
}
|
||||
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
if (UTILS_UNLIKELY(mBoundProgram == p)) {
|
||||
// program didn't change, don't do anything.
|
||||
return true;
|
||||
}
|
||||
bool success = true;
|
||||
if (mBoundProgram != p) {
|
||||
// compile/link the program if needed and call glUseProgram
|
||||
success = p->use(this, mContext);
|
||||
assert_invariant(success == p->isValid());
|
||||
if (success) {
|
||||
// TODO: we could even improve this if the program could tell us which of the descriptors
|
||||
// bindings actually changed. In practice, it is likely that set 0 or 1 might not
|
||||
// change often.
|
||||
decltype(mInvalidDescriptorSetBindings) changed;
|
||||
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
|
||||
mInvalidDescriptorSetBindings |= changed;
|
||||
|
||||
// compile/link the program if needed and call glUseProgram
|
||||
bool const success = p->use(this, mContext);
|
||||
assert_invariant(success == p->isValid());
|
||||
|
||||
if (success) {
|
||||
// TODO: we could even improve this if the program could tell us which of the descriptors
|
||||
// bindings actually changed. In practice, it is likely that set 0 or 1 might not
|
||||
// change often.
|
||||
decltype(mInvalidDescriptorSetBindings) changed;
|
||||
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
|
||||
mInvalidDescriptorSetBindings |= changed;
|
||||
|
||||
mBoundProgram = p;
|
||||
mBoundProgram = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2() && success)) {
|
||||
@@ -1661,7 +1682,7 @@ void OpenGLDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
|
||||
// See if we need the emulated rec709 output conversion
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
sc->rec709 = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE &&
|
||||
sc->rec709 = ((flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) &&
|
||||
!mPlatform.isSRGBSwapChainSupported());
|
||||
}
|
||||
}
|
||||
@@ -3448,7 +3469,7 @@ void OpenGLDriver::beginFrame(
|
||||
UTILS_UNUSED int64_t monotonic_clock_ns,
|
||||
UTILS_UNUSED int64_t refreshIntervalNs,
|
||||
UTILS_UNUSED uint32_t frameId) {
|
||||
DEBUG_MARKER()
|
||||
PROFILE_MARKER(PROFILE_NAME_BEGINFRAME)
|
||||
auto& gl = mContext;
|
||||
insertEventMarker("beginFrame");
|
||||
mPlatform.beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
|
||||
@@ -3483,7 +3504,7 @@ void OpenGLDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
}
|
||||
|
||||
void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
|
||||
DEBUG_MARKER()
|
||||
PROFILE_MARKER(PROFILE_NAME_ENDFRAME)
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
// WebGL builds are single-threaded so users might manipulate various GL state after we're
|
||||
// done with the frame. We do NOT officially support using Filament in this way, but we can
|
||||
@@ -3875,6 +3896,14 @@ void OpenGLDriver::bindDescriptorSet(
|
||||
backend::DescriptorSetHandle dsh,
|
||||
backend::descriptor_set_t set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
|
||||
if (UTILS_UNLIKELY(!dsh)) {
|
||||
mBoundDescriptorSets[set].dsh = dsh;
|
||||
mInvalidDescriptorSetBindings.set(set, true);
|
||||
mInvalidDescriptorSetBindingOffsets.set(set, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle_cast<> here also serves to validate the handle (it actually cannot return nullptr)
|
||||
GLDescriptorSet const* const ds = handle_cast<GLDescriptorSet*>(dsh);
|
||||
if (ds) {
|
||||
|
||||
@@ -174,7 +174,8 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SAMPLER: {
|
||||
case DescriptorType::SAMPLER:
|
||||
case DescriptorType::SAMPLER_EXTERNAL: {
|
||||
if (!entry.name.empty()) {
|
||||
GLint const loc = glGetUniformLocation(program, entry.name.c_str());
|
||||
if (loc >= 0) {
|
||||
@@ -254,7 +255,9 @@ void OpenGLProgram::updateUniforms(
|
||||
for (size_t i = 0, c = records.uniforms.size(); i < c; i++) {
|
||||
Program::Uniform const& u = records.uniforms[i];
|
||||
GLint const loc = records.locations[i];
|
||||
if (loc < 0) {
|
||||
// mRec709Location is special, it is handled by setRec709ColorSpace() and the corresponding
|
||||
// entry in `buffer` is typically not initialized, so we skip it.
|
||||
if (loc < 0 || loc == mRec709Location) {
|
||||
continue;
|
||||
}
|
||||
// u.offset is in 'uint32_t' units
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
@@ -41,13 +42,56 @@ struct OSMesaSwapchain {
|
||||
std::unique_ptr<uint8_t[]> buffer;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
struct OSMesaAPI {
|
||||
private:
|
||||
using CreateContextFunc = OSMesaContext (*)(GLenum format, OSMesaContext);
|
||||
using DestroyContextFunc = GLboolean (*)(OSMesaContext);
|
||||
using MakeCurrentFunc = GLboolean (*)(OSMesaContext ctx, void* buffer, GLenum type,
|
||||
GLsizei width, GLsizei height);
|
||||
using GetProcAddressFunc = OSMESAproc (*)(const char* funcName);
|
||||
|
||||
public:
|
||||
CreateContextFunc OSMesaCreateContext;
|
||||
DestroyContextFunc OSMesaDestroyContext;
|
||||
MakeCurrentFunc OSMesaMakeCurrent;
|
||||
GetProcAddressFunc OSMesaGetProcAddress;
|
||||
|
||||
OSMesaAPI() {
|
||||
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
|
||||
for (char const* libName: libraryNames) {
|
||||
mLib = dlopen(libName, RTLD_GLOBAL | RTLD_NOW);
|
||||
if (mLib) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mLib)
|
||||
<< "Unable to dlopen libOSMesa to create a software GL context";
|
||||
|
||||
OSMesaGetProcAddress = (GetProcAddressFunc) dlsym(mLib, "OSMesaGetProcAddress");
|
||||
|
||||
OSMesaCreateContext = (CreateContextFunc) OSMesaGetProcAddress("OSMesaCreateContext");
|
||||
OSMesaDestroyContext =
|
||||
(DestroyContextFunc) OSMesaGetProcAddress("OSMesaDestroyContext");
|
||||
OSMesaMakeCurrent = (MakeCurrentFunc) OSMesaGetProcAddress("OSMesaMakeCurrent");
|
||||
}
|
||||
|
||||
~OSMesaAPI() {
|
||||
dlclose(mLib);
|
||||
}
|
||||
private:
|
||||
void* mLib = nullptr;
|
||||
};
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
|
||||
const DriverConfig& driverConfig) noexcept {
|
||||
OSMesaAPI* api = new OSMesaAPI();
|
||||
mOsMesaApi = api;
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr)
|
||||
<< "shared GL context is not supported with PlatformOSMesa";
|
||||
mContext = OSMesaCreateContext(GL_RGBA, NULL);
|
||||
mContext = api->OSMesaCreateContext(GL_RGBA, NULL);
|
||||
|
||||
// We need to do a no-op makecurrent here so that the context will be in a correct state before
|
||||
// any GL calls.
|
||||
@@ -62,7 +106,11 @@ Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
|
||||
}
|
||||
|
||||
void PlatformOSMesa::terminate() noexcept {
|
||||
OSMesaDestroyContext(mContext);
|
||||
OSMesaAPI* api = (OSMesaAPI*) mOsMesaApi;
|
||||
api->OSMesaDestroyContext(mContext);
|
||||
delete api;
|
||||
mOsMesaApi = nullptr;
|
||||
|
||||
bluegl::unbind();
|
||||
}
|
||||
|
||||
@@ -84,11 +132,12 @@ void PlatformOSMesa::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
|
||||
|
||||
bool PlatformOSMesa::makeCurrent(ContextType type, SwapChain* drawSwapChain,
|
||||
SwapChain* readSwapChain) noexcept {
|
||||
OSMesaAPI* api = (OSMesaAPI*) mOsMesaApi;
|
||||
OSMesaSwapchain* impl = (OSMesaSwapchain*) drawSwapChain;
|
||||
|
||||
auto result = OSMesaMakeCurrent(mContext, (BackingType*) impl->buffer.get(), BACKING_GL_TYPE,
|
||||
impl->width, impl->height);
|
||||
FILAMENT_CHECK_POSTCONDITION(result) << "OSMesaMakeCurrent failed!";
|
||||
auto result = api->OSMesaMakeCurrent(mContext, (BackingType*) impl->buffer.get(),
|
||||
BACKING_GL_TYPE, impl->width, impl->height);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == GL_TRUE) << "OSMesaMakeCurrent failed!";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
#include <Wingdi.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// this variable is checked in BlueGL.h (included from "gl_headers.h" right after this),
|
||||
// and prevents duplicate definition of OpenGL apis when building this file.
|
||||
// this variable is checked in BlueGL.h (included from "gl_headers.h" right after this),
|
||||
// and prevents duplicate definition of OpenGL apis when building this file.
|
||||
// However, GL_GLEXT_PROTOTYPES need to be defined in BlueGL.h when included from other files.
|
||||
#define FILAMENT_PLATFORM_WGL
|
||||
#endif
|
||||
@@ -37,9 +37,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
void reportLastWindowsError() {
|
||||
void reportWindowsError(DWORD dwError) {
|
||||
LPSTR lpMessageBuffer = nullptr;
|
||||
DWORD dwError = GetLastError();
|
||||
|
||||
if (dwError == 0) {
|
||||
return;
|
||||
@@ -80,6 +79,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
int result = 0;
|
||||
int pixelFormat = 0;
|
||||
DWORD dwError = 0;
|
||||
|
||||
mPfd = {
|
||||
sizeof(PIXELFORMATDESCRIPTOR),
|
||||
@@ -105,6 +105,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
mHWnd = CreateWindowA("STATIC", "dummy", 0, 0, 0, 1, 1, NULL, NULL, NULL, NULL);
|
||||
HDC whdc = mWhdc = GetDC(mHWnd);
|
||||
if (whdc == NULL) {
|
||||
dwError = GetLastError();
|
||||
utils::slog.e << "CreateWindowA() failed" << utils::io::endl;
|
||||
goto error;
|
||||
}
|
||||
@@ -115,6 +116,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
// We need a tmp context to retrieve and call wglCreateContextAttribsARB.
|
||||
tempContext = wglCreateContext(whdc);
|
||||
if (!wglMakeCurrent(whdc, tempContext)) {
|
||||
dwError = GetLastError();
|
||||
utils::slog.e << "wglMakeCurrent() failed, whdc=" << whdc << ", tempContext=" <<
|
||||
tempContext << utils::io::endl;
|
||||
goto error;
|
||||
@@ -136,6 +138,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
if (mContext) {
|
||||
break;
|
||||
}
|
||||
dwError = GetLastError();
|
||||
}
|
||||
|
||||
if (!mContext) {
|
||||
@@ -148,6 +151,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
tempContext = NULL;
|
||||
|
||||
if (!wglMakeCurrent(whdc, mContext)) {
|
||||
dwError = GetLastError();
|
||||
utils::slog.e << "wglMakeCurrent() failed, whdc=" << whdc << ", mContext=" <<
|
||||
mContext << utils::io::endl;
|
||||
goto error;
|
||||
@@ -162,7 +166,7 @@ error:
|
||||
if (tempContext) {
|
||||
wglDeleteContext(tempContext);
|
||||
}
|
||||
reportLastWindowsError();
|
||||
reportWindowsError(dwError);
|
||||
terminate();
|
||||
return NULL;
|
||||
}
|
||||
@@ -205,9 +209,11 @@ Platform::SwapChain* PlatformWGL::createSwapChain(void* nativeWindow, uint64_t f
|
||||
// on Windows, the nativeWindow maps to a HWND
|
||||
swapChain->hWnd = (HWND) nativeWindow;
|
||||
swapChain->hDc = GetDC(swapChain->hWnd);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(swapChain->hDc,
|
||||
"Unable to create the SwapChain (nativeWindow = %p)", nativeWindow)) {
|
||||
reportLastWindowsError();
|
||||
if (!swapChain->hDc) {
|
||||
DWORD dwError = GetLastError();
|
||||
ASSERT_POSTCONDITION_NON_FATAL(swapChain->hDc,
|
||||
"Unable to create the SwapChain (nativeWindow = %p)", nativeWindow);
|
||||
reportWindowsError(dwError);
|
||||
}
|
||||
|
||||
// We have to match pixel formats across the HDC and HGLRC (mContext)
|
||||
@@ -264,8 +270,10 @@ bool PlatformWGL::makeCurrent(ContextType type, SwapChain* drawSwapChain,
|
||||
HDC hdc = wglSwapChain->hDc;
|
||||
if (hdc != NULL) {
|
||||
BOOL success = wglMakeCurrent(hdc, mContext);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(success, "wglMakeCurrent() failed. hdc = %p", hdc)) {
|
||||
reportLastWindowsError();
|
||||
if (!success) {
|
||||
DWORD dwError = GetLastError();
|
||||
ASSERT_POSTCONDITION_NON_FATAL(success, "wglMakeCurrent() failed. hdc = %p", hdc);
|
||||
reportWindowsError(dwError);
|
||||
wglMakeCurrent(0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 "VulkanAsyncHandles.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
|
||||
: mStartingQueryIndex(std::get<0>(indices)),
|
||||
mStoppingQueryIndex(std::get<1>(indices)) {}
|
||||
|
||||
void VulkanTimerQuery::setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mFenceMutex);
|
||||
mFence = fence;
|
||||
}
|
||||
|
||||
bool VulkanTimerQuery::isCompleted() noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mFenceMutex);
|
||||
// QueryValue is a synchronous call and might occur before beginTimerQuery has written anything
|
||||
// into the command buffer, which is an error according to the validation layer that ships in
|
||||
// the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to require that the
|
||||
// timestamp has at least been written into a processed command buffer.
|
||||
|
||||
// This fence indicates that the corresponding buffer has been completed.
|
||||
return mFence && mFence->getStatus() == VK_SUCCESS;
|
||||
}
|
||||
|
||||
VulkanTimerQuery::~VulkanTimerQuery() = default;
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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_VULKANASYNCHANDLES_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANASYNCHANDLES_H
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "vulkan/memory/Resource.h"
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Condition.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
|
||||
struct VulkanCmdFence {
|
||||
VulkanCmdFence(VkResult initialStatus) {
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
|
||||
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
|
||||
// finishes executing the command buffer, the status changes to VK_SUCCESS.
|
||||
status.store(initialStatus);
|
||||
}
|
||||
|
||||
~VulkanCmdFence() = default;
|
||||
|
||||
void setStatus(VkResult value) { status.store(value); }
|
||||
|
||||
VkResult getStatus() { return status.load(std::memory_order_acquire); }
|
||||
|
||||
private:
|
||||
std::atomic<VkResult> status;
|
||||
};
|
||||
|
||||
struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
|
||||
VulkanFence() {}
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
};
|
||||
|
||||
struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource {
|
||||
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
|
||||
~VulkanTimerQuery();
|
||||
|
||||
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept;
|
||||
|
||||
bool isCompleted() noexcept;
|
||||
|
||||
uint32_t getStartingQueryIndex() const {
|
||||
return mStartingQueryIndex;
|
||||
}
|
||||
|
||||
uint32_t getStoppingQueryIndex() const {
|
||||
return mStoppingQueryIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mStartingQueryIndex;
|
||||
uint32_t mStoppingQueryIndex;
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> mFence;
|
||||
utils::Mutex mFenceMutex;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANHASYNCANDLES_H
|
||||
@@ -126,8 +126,7 @@ struct BlitterUniforms {
|
||||
}// anonymous namespace
|
||||
|
||||
VulkanBlitter::VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept
|
||||
: mPhysicalDevice(physicalDevice),
|
||||
mCommands(commands) {}
|
||||
: mPhysicalDevice(physicalDevice), mCommands(commands) {}
|
||||
|
||||
void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
|
||||
|
||||
@@ -151,7 +150,8 @@ void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
|
||||
}
|
||||
#endif
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VulkanCommandBuffer& commands = dst.texture->getIsProtected() ?
|
||||
mCommands->getProtected() : mCommands->get();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
resolveFast(&commands, aspect, src, dst);
|
||||
@@ -176,7 +176,8 @@ void VulkanBlitter::blit(VkFilter filter,
|
||||
#endif
|
||||
// src and dst should have the same aspect here
|
||||
VkImageAspectFlags const aspect = src.texture->getImageAspect();
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VulkanCommandBuffer& commands = dst.texture->getIsProtected() ?
|
||||
mCommands->getProtected() : mCommands->get();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(&commands, aspect, filter, src, dst, srcRectPair, dstRectPair);
|
||||
|
||||
@@ -33,275 +33,194 @@ using namespace utils;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
using Timestamp = VulkanGroupMarkers::Timestamp;
|
||||
#endif
|
||||
|
||||
VulkanCmdFence::VulkanCmdFence(VkFence ifence)
|
||||
: fence(ifence) {
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence gets
|
||||
// submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually finishes
|
||||
// executing the command buffer, the status changes to VK_SUCCESS.
|
||||
status.store(VK_INCOMPLETE);
|
||||
}
|
||||
|
||||
VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device,
|
||||
VkCommandPool pool)
|
||||
: mResourceManager(allocator),
|
||||
mPipeline(VK_NULL_HANDLE) {
|
||||
VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) {
|
||||
VkCommandBuffer cmdbuffer;
|
||||
// Create the low-level command buffer.
|
||||
const VkCommandBufferAllocateInfo allocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
VkCommandBufferAllocateInfo const allocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
|
||||
// The buffer allocated here will be implicitly reset when vkBeginCommandBuffer is called.
|
||||
// We don't need to deallocate since destroying the pool will free all of the buffers.
|
||||
vkAllocateCommandBuffers(device, &allocateInfo, &mBuffer);
|
||||
vkAllocateCommandBuffers(device, &allocateInfo, &cmdbuffer);
|
||||
return cmdbuffer;
|
||||
}
|
||||
|
||||
CommandBufferObserver::~CommandBufferObserver() {}
|
||||
|
||||
static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) {
|
||||
VkCommandPoolCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
|
||||
| VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
|
||||
.queueFamilyIndex = queueFamilyIndex,
|
||||
};
|
||||
VkCommandPool pool;
|
||||
vkCreateCommandPool(device, &createInfo, VKALLOC, &pool);
|
||||
return pool;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
|
||||
mMarkers.push_back(marker);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
mTimestamps.push_back(start.time_since_epoch().count() > 0.0
|
||||
? start
|
||||
: std::chrono::high_resolution_clock::now());
|
||||
#endif
|
||||
mMarkers.push_back({marker,
|
||||
start.time_since_epoch().count() > 0.0
|
||||
? start
|
||||
: std::chrono::high_resolution_clock::now()});
|
||||
}
|
||||
|
||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
|
||||
auto const marker = mMarkers.back();
|
||||
auto ret = mMarkers.back();
|
||||
mMarkers.pop_back();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
auto const timestamp = mTimestamps.back();
|
||||
mTimestamps.pop_back();
|
||||
return std::make_pair(marker, timestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
|
||||
auto const marker = mMarkers.front();
|
||||
auto ret = mMarkers.front();
|
||||
mMarkers.pop_front();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
auto const timestamp = mTimestamps.front();
|
||||
mTimestamps.pop_front();
|
||||
return std::make_pair(marker, timestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
|
||||
std::pair<std::string, Timestamp> const& VulkanGroupMarkers::top() const {
|
||||
assert_invariant(!empty());
|
||||
auto const marker = mMarkers.back();
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
auto const topTimestamp = mTimestamps.front();
|
||||
return std::make_pair(marker, topTimestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
#endif
|
||||
return mMarkers.back();
|
||||
}
|
||||
|
||||
bool VulkanGroupMarkers::empty() const noexcept {
|
||||
return mMarkers.empty();
|
||||
}
|
||||
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VulkanContext* context, VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext* context, VkDevice device, VkQueue queue,
|
||||
VkCommandPool pool, bool isProtected)
|
||||
: mContext(context),
|
||||
mMarkerCount(0),
|
||||
isProtected(isProtected),
|
||||
mDevice(device),
|
||||
mQueue(queue),
|
||||
mPool(createPool(mDevice, queueFamilyIndex)),
|
||||
mContext(context),
|
||||
mStorage(CAPACITY) {
|
||||
mBuffer(createCommandBuffer(device, pool)),
|
||||
mFenceStatus(std::make_shared<VulkanCmdFence>(VK_INCOMPLETE)) {
|
||||
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
for (auto& semaphore: mSubmissionSignals) {
|
||||
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
|
||||
}
|
||||
vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission);
|
||||
|
||||
VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
for (auto& fence: mFences) {
|
||||
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator, mDevice, mPool);
|
||||
}
|
||||
|
||||
#if !FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
(void) mContext;
|
||||
#endif
|
||||
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence);
|
||||
}
|
||||
|
||||
void VulkanCommands::terminate() {
|
||||
wait();
|
||||
gc();
|
||||
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
|
||||
for (VkSemaphore sema: mSubmissionSignals) {
|
||||
vkDestroySemaphore(mDevice, sema, VKALLOC);
|
||||
VulkanCommandBuffer::~VulkanCommandBuffer() {
|
||||
vkDestroySemaphore(mDevice, mSubmission, VKALLOC);
|
||||
vkDestroyFence(mDevice, mFence, VKALLOC);
|
||||
}
|
||||
|
||||
void VulkanCommandBuffer::reset() noexcept {
|
||||
mMarkerCount = 0;
|
||||
mResources.clear();
|
||||
mWaitSemaphores.clear();
|
||||
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
|
||||
// gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
|
||||
// finishes executing the command buffer, the status changes to VK_SUCCESS.
|
||||
mFenceStatus = std::make_shared<VulkanCmdFence>(VK_INCOMPLETE);
|
||||
vkResetFences(mDevice, 1, &mFence);
|
||||
}
|
||||
|
||||
void VulkanCommandBuffer::pushMarker(char const* marker) noexcept {
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
.pLabelName = marker,
|
||||
.color = {0, 1, 0, 1},
|
||||
};
|
||||
vkCmdBeginDebugUtilsLabelEXT(mBuffer, &labelInfo);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = marker,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
vkCmdDebugMarkerBeginEXT(mBuffer, &markerInfo);
|
||||
}
|
||||
for (VkFence fence: mFences) {
|
||||
vkDestroyFence(mDevice, fence, VKALLOC);
|
||||
mMarkerCount++;
|
||||
}
|
||||
|
||||
void VulkanCommandBuffer::popMarker() noexcept{
|
||||
assert_invariant(mMarkerCount > 0);
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
vkCmdEndDebugUtilsLabelEXT(mBuffer);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
vkCmdDebugMarkerEndEXT(mBuffer);
|
||||
}
|
||||
mMarkerCount--;
|
||||
}
|
||||
|
||||
void VulkanCommandBuffer::insertEvent(char const* marker) noexcept {
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
.pLabelName = marker,
|
||||
.color = {1, 1, 0, 1},
|
||||
};
|
||||
vkCmdInsertDebugUtilsLabelEXT(mBuffer, &labelInfo);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = marker,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
vkCmdDebugMarkerInsertEXT(mBuffer, &markerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& VulkanCommands::get() {
|
||||
if (mCurrentCommandBufferIndex >= 0) {
|
||||
return *mStorage[mCurrentCommandBufferIndex].get();
|
||||
}
|
||||
|
||||
// If we ran out of available command buffers, stall until one finishes. This is very rare.
|
||||
// It occurs only when Filament invokes commit() or endFrame() a large number of times without
|
||||
// presenting the swap chain or waiting on a fence.
|
||||
while (mAvailableBufferCount == 0) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
FVK_LOGI << "VulkanCommands has stalled. "
|
||||
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
|
||||
<< io::endl;
|
||||
#endif
|
||||
wait();
|
||||
gc();
|
||||
}
|
||||
|
||||
VulkanCommandBuffer* currentbuf = nullptr;
|
||||
// Find an available slot.
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() == VK_NULL_HANDLE) {
|
||||
mCurrentCommandBufferIndex = static_cast<int8_t>(i);
|
||||
currentbuf = wrapper;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_invariant(currentbuf);
|
||||
mAvailableBufferCount--;
|
||||
|
||||
// Note that the fence wrapper uses shared_ptr because a DriverAPI fence can also have ownership
|
||||
// over it. The destruction of the low-level fence occurs either in VulkanCommands::gc(), or in
|
||||
// VulkanDriver::destroyFence(), both of which are safe spots.
|
||||
currentbuf->fence = std::make_shared<VulkanCmdFence>(mFences[mCurrentCommandBufferIndex]);
|
||||
|
||||
void VulkanCommandBuffer::begin() noexcept {
|
||||
// Begin writing into the command buffer.
|
||||
const VkCommandBufferBeginInfo binfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
VkCommandBufferBeginInfo const binfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
vkBeginCommandBuffer(currentbuf->buffer(), &binfo);
|
||||
|
||||
// Notify the observer that a new command buffer has been activated.
|
||||
if (mObserver) {
|
||||
mObserver->onCommandBuffer(*currentbuf);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
// We push the current markers onto a temporary stack. This must be placed after currentbuf is
|
||||
// set to the new command buffer since pushGroupMarker also calls get().
|
||||
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||
auto [marker, time] = mCarriedOverMarkers->pop();
|
||||
pushGroupMarker(marker.c_str(), time);
|
||||
}
|
||||
#endif
|
||||
return *currentbuf;
|
||||
vkBeginCommandBuffer(mBuffer, &binfo);
|
||||
}
|
||||
|
||||
bool VulkanCommands::flush() {
|
||||
// It's perfectly fine to call flush when no commands have been written.
|
||||
if (mCurrentCommandBufferIndex < 0) {
|
||||
return false;
|
||||
VkSemaphore VulkanCommandBuffer::submit() {
|
||||
while (mMarkerCount > 0) {
|
||||
popMarker();
|
||||
}
|
||||
|
||||
// Before actually submitting, we need to pop any leftover group markers.
|
||||
// Note that this needs to occur before vkEndCommandBuffer.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
while (mGroupMarkers && !mGroupMarkers->empty()) {
|
||||
if (!mCarriedOverMarkers) {
|
||||
mCarriedOverMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
}
|
||||
auto const [marker, time] = mGroupMarkers->top();
|
||||
mCarriedOverMarkers->push(marker, time);
|
||||
// We still need to call through to vkCmdEndDebugUtilsLabelEXT.
|
||||
popGroupMarker();
|
||||
}
|
||||
#endif
|
||||
vkEndCommandBuffer(mBuffer);
|
||||
|
||||
int8_t const index = mCurrentCommandBufferIndex;
|
||||
VulkanCommandBuffer const* currentbuf = mStorage[index].get();
|
||||
VkSemaphore const renderingFinished = mSubmissionSignals[index];
|
||||
|
||||
vkEndCommandBuffer(currentbuf->buffer());
|
||||
|
||||
// If the injected semaphore is an "image available" semaphore that has not yet been signaled,
|
||||
// it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the
|
||||
// VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage. However we need to assume the worst
|
||||
// here and use VK_PIPELINE_STAGE_ALL_COMMANDS_BIT. This is a more aggressive stall, but it is
|
||||
// the only safe option because the previously submitted command buffer might have set up some
|
||||
// state that the new command buffer depends on.
|
||||
VkPipelineStageFlags waitDestStageMasks[2] = {
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VkPipelineStageFlags const waitDestStageMasks[2] = {
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
};
|
||||
|
||||
VkSemaphore signals[2] = {
|
||||
VK_NULL_HANDLE,
|
||||
VK_NULL_HANDLE,
|
||||
};
|
||||
uint32_t waitSemaphoreCount = 0;
|
||||
if (mSubmissionSignal) {
|
||||
signals[waitSemaphoreCount++] = mSubmissionSignal;
|
||||
}
|
||||
if (mInjectedSignal) {
|
||||
signals[waitSemaphoreCount++] = mInjectedSignal;
|
||||
}
|
||||
VkCommandBuffer const cmdbuffer = currentbuf->buffer();
|
||||
VkSubmitInfo submitInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = waitSemaphoreCount,
|
||||
.pWaitSemaphores = waitSemaphoreCount > 0 ? signals : nullptr,
|
||||
.pWaitDstStageMask = waitDestStageMasks,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmdbuffer,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &renderingFinished,
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = mWaitSemaphores.size(),
|
||||
.pWaitSemaphores = mWaitSemaphores.data(),
|
||||
.pWaitDstStageMask = waitDestStageMasks,
|
||||
.commandBufferCount = 1u,
|
||||
.pCommandBuffers = &mBuffer,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &mSubmission,
|
||||
};
|
||||
// add submit protection if needed
|
||||
VkProtectedSubmitInfo protectedSubmitInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO,
|
||||
.protectedSubmit = VK_TRUE,
|
||||
};
|
||||
|
||||
if (isProtected) {
|
||||
submitInfo.pNext = &protectedSubmitInfo;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
FVK_LOGI << "Submitting cmdbuffer=" << cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< " fence=" << currentbuf->fence->fence
|
||||
<< utils::io::endl;
|
||||
FVK_LOGI << "Submitting cmdbuffer=" << mBuffer
|
||||
<< " wait=(";
|
||||
for (size_t s = 0, count = mWaitSemaphores.size(); s < count; ++s) {
|
||||
FVK_LOGI << mWaitSemaphores[s] << " ";
|
||||
}
|
||||
FVK_LOGI << ") "
|
||||
<< " signal=" << mSubmission
|
||||
<< " fence=" << mFence << utils::io::endl;
|
||||
#endif
|
||||
|
||||
auto& cmdfence = currentbuf->fence;
|
||||
UTILS_UNUSED_IN_RELEASE VkResult result = VK_SUCCESS;
|
||||
{
|
||||
auto scope = cmdfence->setValue(VK_NOT_READY);
|
||||
result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->getFence());
|
||||
}
|
||||
mFenceStatus->setStatus(VK_NOT_READY);
|
||||
UTILS_UNUSED_IN_RELEASE VkResult result =
|
||||
vkQueueSubmit(mQueue, 1, &submitInfo, mFence);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
if (result != VK_SUCCESS) {
|
||||
@@ -309,176 +228,298 @@ bool VulkanCommands::flush() {
|
||||
}
|
||||
#endif
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
mWaitSemaphores.clear();
|
||||
return mSubmission;
|
||||
}
|
||||
|
||||
CommandBufferPool::CommandBufferPool(VulkanContext* context, VkDevice device, VkQueue queue,
|
||||
uint8_t queueFamilyIndex, bool isProtected)
|
||||
: mDevice(device),
|
||||
mRecording(INVALID) {
|
||||
VkCommandPoolCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT |
|
||||
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
|
||||
(isProtected ? VK_COMMAND_POOL_CREATE_PROTECTED_BIT : 0u),
|
||||
.queueFamilyIndex = queueFamilyIndex,
|
||||
};
|
||||
vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool);
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
mBuffers.emplace_back(
|
||||
std::make_unique<VulkanCommandBuffer>(context, device, queue, mPool, isProtected));
|
||||
}
|
||||
}
|
||||
|
||||
CommandBufferPool::~CommandBufferPool() {
|
||||
wait();
|
||||
gc();
|
||||
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& CommandBufferPool::getRecording() {
|
||||
if (isRecording()) {
|
||||
return *mBuffers[mRecording];
|
||||
}
|
||||
|
||||
auto const findNext = [this]() {
|
||||
for (int8_t i = 0; i < CAPACITY; ++i) {
|
||||
if (!mSubmitted[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INVALID;
|
||||
};
|
||||
|
||||
while ((mRecording = findNext()) == INVALID) {
|
||||
wait();
|
||||
gc();
|
||||
}
|
||||
|
||||
auto& recording = *mBuffers[mRecording];
|
||||
recording.begin();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
if (mGroupMarkers) {
|
||||
std::unique_ptr<VulkanGroupMarkers> markers = std::make_unique<VulkanGroupMarkers>();
|
||||
while (!mGroupMarkers->empty()) {
|
||||
auto [marker, timestamp] = mGroupMarkers->pop_bottom();
|
||||
recording.pushMarker(marker.c_str());
|
||||
markers->push(marker, timestamp);
|
||||
}
|
||||
std::swap(mGroupMarkers, markers);
|
||||
}
|
||||
#endif
|
||||
|
||||
return recording;
|
||||
}
|
||||
|
||||
void CommandBufferPool::gc() {
|
||||
ActiveBuffers reclaimed;
|
||||
mSubmitted.forEachSetBit([this,&reclaimed] (size_t index) {
|
||||
auto& buffer = mBuffers[index];
|
||||
if (buffer->getStatus() == VK_SUCCESS) {
|
||||
reclaimed.set(index, true);
|
||||
buffer->reset();
|
||||
}
|
||||
});
|
||||
mSubmitted &= ~reclaimed;
|
||||
}
|
||||
|
||||
void CommandBufferPool::update() {
|
||||
mSubmitted.forEachSetBit([this] (size_t index) {
|
||||
auto& buffer = mBuffers[index];
|
||||
VkResult status = vkGetFenceStatus(mDevice, buffer->getVkFence());
|
||||
if (status == VK_SUCCESS) {
|
||||
buffer->setComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
VkSemaphore CommandBufferPool::flush() {
|
||||
// We're not recording right now.
|
||||
if (!isRecording()) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
auto submitSemaphore = mBuffers[mRecording]->submit();
|
||||
mSubmitted.set(mRecording, true);
|
||||
mRecording = INVALID;
|
||||
return submitSemaphore;
|
||||
}
|
||||
|
||||
void CommandBufferPool::wait() {
|
||||
uint8_t count = 0;
|
||||
VkFence fences[CAPACITY];
|
||||
mSubmitted.forEachSetBit([this, &count, &fences] (size_t index) {
|
||||
fences[count++] = mBuffers[index]->getVkFence();
|
||||
});
|
||||
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
|
||||
update();
|
||||
}
|
||||
|
||||
void CommandBufferPool::waitFor(VkSemaphore previousAction) {
|
||||
if (!isRecording()) {
|
||||
return;
|
||||
}
|
||||
auto& recording = mBuffers[mRecording];
|
||||
recording->insertWait(previousAction);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
std::string CommandBufferPool::topMarker() const {
|
||||
if (!mGroupMarkers || mGroupMarkers->empty()) {
|
||||
return "";
|
||||
}
|
||||
return std::get<0>(mGroupMarkers->top());
|
||||
}
|
||||
|
||||
void CommandBufferPool::pushMarker(char const* marker, VulkanGroupMarkers::Timestamp timestamp) {
|
||||
if (!mGroupMarkers) {
|
||||
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
}
|
||||
mGroupMarkers->push(marker, timestamp);
|
||||
getRecording().pushMarker(marker);
|
||||
}
|
||||
|
||||
std::pair<std::string, VulkanGroupMarkers::Timestamp> CommandBufferPool::popMarker() {
|
||||
assert_invariant(mGroupMarkers && !mGroupMarkers->empty());
|
||||
auto ret = mGroupMarkers->pop();
|
||||
|
||||
// Note that if we're popping a marker while not recording, we would just pop the conceptual
|
||||
// stack of marker (i.e. mGroupMarkers) and not carry out the pop on the command buffer.
|
||||
if (isRecording()) {
|
||||
getRecording().popMarker();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CommandBufferPool::insertEvent(char const* marker) {
|
||||
getRecording().insertEvent(marker);
|
||||
}
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context)
|
||||
: mDevice(device),
|
||||
mProtectedQueue(protectedQueue),
|
||||
mProtectedQueueFamilyIndex(protectedQueueFamilyIndex),
|
||||
mContext(context),
|
||||
mPool(std::make_unique<CommandBufferPool>(context, device, queue, queueFamilyIndex, false)) {}
|
||||
|
||||
void VulkanCommands::terminate() {
|
||||
mPool.reset();
|
||||
mProtectedPool.reset();
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& VulkanCommands::get() {
|
||||
auto& ret = mPool->getRecording();
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& VulkanCommands::getProtected() {
|
||||
assert_invariant(mProtectedQueue != VK_NULL_HANDLE);
|
||||
|
||||
if (!mProtectedPool) {
|
||||
mProtectedPool = std::make_unique<CommandBufferPool>(mContext, mDevice, mProtectedQueue,
|
||||
mProtectedQueueFamilyIndex, true);
|
||||
}
|
||||
auto& ret = mProtectedPool->getRecording();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VulkanCommands::flush() {
|
||||
// It's possible to call flush and wait at "terminate", in which case, we'll just return.
|
||||
if (!mPool && !mProtectedPool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkSemaphore dependency = mInjectedDependency;
|
||||
VkSemaphore lastSubmit = mLastSubmit;
|
||||
bool hasFlushed = false;
|
||||
|
||||
// Note that we've ordered it so that the non-protected commands are followed by the protected
|
||||
// commands. This assumes that the protected commands will be that one doing the rendering into
|
||||
// the protected memory (i.e. protected render target).
|
||||
for (auto pool: {mPool.get(), mProtectedPool.get()}) {
|
||||
if (!pool || !pool->isRecording()) {
|
||||
continue;
|
||||
}
|
||||
if (dependency != VK_NULL_HANDLE) {
|
||||
pool->waitFor(dependency);
|
||||
}
|
||||
if (lastSubmit != VK_NULL_HANDLE) {
|
||||
pool->waitFor(lastSubmit);
|
||||
lastSubmit = VK_NULL_HANDLE;
|
||||
}
|
||||
dependency = pool->flush();
|
||||
hasFlushed = true;
|
||||
}
|
||||
|
||||
if (hasFlushed) {
|
||||
mInjectedDependency = VK_NULL_HANDLE;
|
||||
mLastSubmit = dependency;
|
||||
}
|
||||
|
||||
mSubmissionSignal = renderingFinished;
|
||||
mInjectedSignal = VK_NULL_HANDLE;
|
||||
mCurrentCommandBufferIndex = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
VkSemaphore VulkanCommands::acquireFinishedSignal() {
|
||||
VkSemaphore semaphore = mSubmissionSignal;
|
||||
mSubmissionSignal = VK_NULL_HANDLE;
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
FVK_LOGI << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||
#endif
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
void VulkanCommands::injectDependency(VkSemaphore next) {
|
||||
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
|
||||
mInjectedSignal = next;
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
FVK_LOGI << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanCommands::wait() {
|
||||
VkFence fences[CAPACITY];
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() != VK_NULL_HANDLE
|
||||
&& mCurrentCommandBufferIndex != static_cast<int8_t>(i)) {
|
||||
fences[count++] = wrapper->fence->getFence();
|
||||
}
|
||||
// It's possible to call flush and wait at "terminate", in which case, we'll just return.
|
||||
if (!mPool && !mProtectedPool) {
|
||||
return;
|
||||
}
|
||||
if (count > 0) {
|
||||
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
|
||||
updateFences();
|
||||
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("commands::wait");
|
||||
|
||||
mPool->wait();
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->wait();
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanCommands::gc() {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("commands::gc");
|
||||
|
||||
VkFence fences[CAPACITY];
|
||||
size_t count = 0;
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
auto const vkfence = wrapper->fence->getFence();
|
||||
VkResult const result = vkGetFenceStatus(mDevice, vkfence);
|
||||
if (result != VK_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
fences[count++] = vkfence;
|
||||
wrapper->fence->setValue(VK_SUCCESS);
|
||||
wrapper->reset();
|
||||
mAvailableBufferCount++;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
vkResetFences(mDevice, count, fences);
|
||||
mPool->gc();
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->gc();
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanCommands::updateFences() {
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() != VK_NULL_HANDLE) {
|
||||
VulkanCmdFence* fence = wrapper->fence.get();
|
||||
if (fence) {
|
||||
VkResult status = vkGetFenceStatus(mDevice, fence->getFence());
|
||||
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
|
||||
fence->setValue(status);
|
||||
}
|
||||
}
|
||||
mPool->update();
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->update();
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
|
||||
void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp) {
|
||||
mPool->pushMarker(str, timestamp);
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->pushMarker(str, timestamp);
|
||||
}
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
// If the timestamp is not 0, then we are carrying over a marker across buffer submits.
|
||||
// If it is 0, then this is a normal marker push and we should just print debug line as usual.
|
||||
if (timestamp.time_since_epoch().count() == 0.0) {
|
||||
FVK_LOGD << "----> " << str << utils::io::endl;
|
||||
}
|
||||
FVK_LOGD << "----> " << str << utils::io::endl;
|
||||
#endif
|
||||
|
||||
// TODO: Add group marker color to the Driver API
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
|
||||
if (!mGroupMarkers) {
|
||||
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
}
|
||||
mGroupMarkers->push(str, timestamp);
|
||||
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
.pLabelName = str,
|
||||
.color = {0, 1, 0, 1},
|
||||
};
|
||||
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = str,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommands::popGroupMarker() {
|
||||
assert_invariant(mGroupMarkers);
|
||||
|
||||
if (!mGroupMarkers->empty()) {
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
auto const [marker, startTime] = mGroupMarkers->pop();
|
||||
auto const endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> diff = endTime - startTime;
|
||||
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
|
||||
<< utils::io::endl;
|
||||
auto ret = mPool->popMarker();
|
||||
auto const& marker = ret.first;
|
||||
auto const& startTime = ret.second;
|
||||
auto const endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> diff = endTime - startTime;
|
||||
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
|
||||
<< utils::io::endl;
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
#endif
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
vkCmdDebugMarkerEndEXT(cmdbuffer);
|
||||
}
|
||||
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||
// It could be that pop is called between flush() and get() (new command buffer), in which
|
||||
// case the marker is in "carried over" state, we'd just remove that. Since the
|
||||
// mCarriedOverMarkers is in the opposite order, we pop the bottom instead of the top.
|
||||
mCarriedOverMarkers->pop_bottom();
|
||||
mPool->popMarker();
|
||||
#endif // FVK_DEBUG_PRINT_GROUP_MARKERS
|
||||
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->popMarker();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommands::insertEventMarker(char const* string, uint32_t len) {
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
.pLabelName = string,
|
||||
.color = {1, 1, 0, 1},
|
||||
};
|
||||
vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = string,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo);
|
||||
void VulkanCommands::insertEventMarker(char const* str, uint32_t len) {
|
||||
mPool->insertEvent(str);
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->insertEvent(str);
|
||||
}
|
||||
}
|
||||
|
||||
std::string VulkanCommands::getTopGroupMarker() const {
|
||||
if (!mGroupMarkers || mGroupMarkers->empty()) {
|
||||
return "";
|
||||
if (mProtectedPool) {
|
||||
return mProtectedPool->topMarker();
|
||||
}
|
||||
return std::get<0>(mGroupMarkers->top());
|
||||
return mPool->topMarker();
|
||||
}
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanAsyncHandles.h"
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <utils/Condition.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
@@ -37,6 +39,8 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace fvkmemory;
|
||||
|
||||
struct VulkanContext;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
@@ -47,111 +51,122 @@ public:
|
||||
void push(std::string const& marker, Timestamp start = {}) noexcept;
|
||||
std::pair<std::string, Timestamp> pop() noexcept;
|
||||
std::pair<std::string, Timestamp> pop_bottom() noexcept;
|
||||
std::pair<std::string, Timestamp> top() const;
|
||||
std::pair<std::string, Timestamp> const& top() const;
|
||||
bool empty() const noexcept;
|
||||
|
||||
private:
|
||||
std::list<std::string> mMarkers;
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
std::list<Timestamp> mTimestamps;
|
||||
#endif
|
||||
std::list<std::pair<std::string, Timestamp>> mMarkers;
|
||||
};
|
||||
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
|
||||
struct VulkanCmdFence {
|
||||
struct SetValueScope {
|
||||
public:
|
||||
~SetValueScope() {
|
||||
mHolder->mutex.unlock();
|
||||
mHolder->condition.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
SetValueScope(VulkanCmdFence* fenceHolder, VkResult result) :
|
||||
mHolder(fenceHolder) {
|
||||
mHolder->mutex.lock();
|
||||
mHolder->status.store(result);
|
||||
}
|
||||
VulkanCmdFence* mHolder;
|
||||
friend struct VulkanCmdFence;
|
||||
};
|
||||
|
||||
VulkanCmdFence(VkFence ifence);
|
||||
~VulkanCmdFence() = default;
|
||||
|
||||
SetValueScope setValue(VkResult value) {
|
||||
return {this, value};
|
||||
}
|
||||
|
||||
VkFence& getFence() {
|
||||
return fence;
|
||||
}
|
||||
|
||||
VkResult getStatus() {
|
||||
std::unique_lock<utils::Mutex> lock(mutex);
|
||||
return status.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
private:
|
||||
VkFence fence;
|
||||
utils::Condition condition;
|
||||
utils::Mutex mutex;
|
||||
std::atomic<VkResult> status;
|
||||
};
|
||||
|
||||
// The submission fence has shared ownership semantics because it is potentially wrapped by a
|
||||
// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and
|
||||
// we're done waiting on the most recent submission of the given command buffer.
|
||||
struct VulkanCommandBuffer {
|
||||
VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, VkCommandPool pool);
|
||||
VulkanCommandBuffer(VulkanContext* mContext,
|
||||
VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected);
|
||||
|
||||
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
|
||||
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
|
||||
|
||||
inline void acquire(VulkanResource* resource) {
|
||||
mResourceManager.acquire(resource);
|
||||
~VulkanCommandBuffer();
|
||||
|
||||
inline void acquire(fvkmemory::resource_ptr<fvkmemory::Resource> resource) {
|
||||
mResources.push_back(resource);
|
||||
}
|
||||
|
||||
inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) {
|
||||
mResourceManager.acquireAll(srcResources);
|
||||
void reset() noexcept;
|
||||
|
||||
inline void insertWait(VkSemaphore sem) {
|
||||
mWaitSemaphores.insert(sem);
|
||||
}
|
||||
|
||||
inline void reset() {
|
||||
fence.reset();
|
||||
mResourceManager.clear();
|
||||
mPipeline = VK_NULL_HANDLE;
|
||||
void pushMarker(char const* marker) noexcept;
|
||||
void popMarker() noexcept;
|
||||
void insertEvent(char const* marker) noexcept;
|
||||
|
||||
void begin() noexcept;
|
||||
VkSemaphore submit();
|
||||
|
||||
inline void setComplete() {
|
||||
mFenceStatus->setStatus(VK_SUCCESS);
|
||||
}
|
||||
|
||||
inline void setPipeline(VkPipeline pipeline) {
|
||||
mPipeline = pipeline;
|
||||
VkResult getStatus() {
|
||||
return mFenceStatus->getStatus();
|
||||
}
|
||||
|
||||
inline VkPipeline pipeline() const {
|
||||
return mPipeline;
|
||||
std::shared_ptr<VulkanCmdFence> getFenceStatus() const {
|
||||
return mFenceStatus;
|
||||
}
|
||||
|
||||
inline VkCommandBuffer buffer() const {
|
||||
if (fence) {
|
||||
return mBuffer;
|
||||
}
|
||||
return VK_NULL_HANDLE;
|
||||
VkFence getVkFence() const {
|
||||
return mFence;
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
VkCommandBuffer buffer() const {
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
private:
|
||||
VulkanAcquireOnlyResourceManager mResourceManager;
|
||||
VulkanContext* mContext;
|
||||
uint8_t mMarkerCount;
|
||||
bool const isProtected;
|
||||
VkDevice mDevice;
|
||||
VkQueue mQueue;
|
||||
CappedArray<VkSemaphore, 2> mWaitSemaphores;
|
||||
VkCommandBuffer mBuffer;
|
||||
VkPipeline mPipeline;
|
||||
VkSemaphore mSubmission;
|
||||
VkFence mFence;
|
||||
std::shared_ptr<VulkanCmdFence> mFenceStatus;
|
||||
std::vector<fvkmemory::resource_ptr<Resource>> mResources;
|
||||
};
|
||||
|
||||
// Allows classes to be notified after a new command buffer has been activated.
|
||||
class CommandBufferObserver {
|
||||
public:
|
||||
virtual void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) = 0;
|
||||
virtual ~CommandBufferObserver();
|
||||
struct CommandBufferPool {
|
||||
using ActiveBuffers = utils::bitset64;
|
||||
static constexpr int8_t INVALID = -1;
|
||||
|
||||
CommandBufferPool(VulkanContext* context, VkDevice device, VkQueue queue,
|
||||
uint8_t queueFamilyIndex, bool isProtected);
|
||||
~CommandBufferPool();
|
||||
|
||||
VulkanCommandBuffer& getRecording();
|
||||
|
||||
void gc();
|
||||
void update();
|
||||
VkSemaphore flush();
|
||||
void wait();
|
||||
void waitFor(VkSemaphore previousAction);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
std::string topMarker() const;
|
||||
void pushMarker(char const* marker, VulkanGroupMarkers::Timestamp timestamp);
|
||||
std::pair<std::string, VulkanGroupMarkers::Timestamp> popMarker();
|
||||
void insertEvent(char const* marker);
|
||||
#endif
|
||||
|
||||
inline bool isRecording() const { return mRecording != INVALID; }
|
||||
|
||||
private:
|
||||
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
|
||||
// int8 only goes up to 127, therefore capacity must be less than that.
|
||||
static_assert(CAPACITY < 128);
|
||||
|
||||
// The number of bits in ActiveBuffers describe the usage of the buffers in the pool, so must be
|
||||
// larger than the size of the pool.
|
||||
static_assert(sizeof(ActiveBuffers) * 8 >= CAPACITY);
|
||||
|
||||
using BufferList = utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>>;
|
||||
VkDevice mDevice;
|
||||
VkCommandPool mPool;
|
||||
ActiveBuffers mSubmitted;
|
||||
std::vector<std::unique_ptr<VulkanCommandBuffer>> mBuffers;
|
||||
int8_t mRecording;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
|
||||
#endif
|
||||
};
|
||||
|
||||
// Manages a set of command buffers and semaphores, exposing an API that is significantly simpler
|
||||
@@ -165,9 +180,6 @@ public:
|
||||
// - This creates a guarantee of in-order execution.
|
||||
// - Semaphores are recycled to prevent create / destroy churn.
|
||||
//
|
||||
// - Notifies listeners when recording begins in a new VkCommandBuffer.
|
||||
// - Used by PipelineCache so that it knows when to clear out its shadow state.
|
||||
//
|
||||
// - Allows 1 user to inject a "dependency" semaphore that stalls the next flush.
|
||||
// - This is used for asynchronous acquisition of a swap chain image, since the GPU
|
||||
// might require a valid swap chain image when it starts executing the command buffer.
|
||||
@@ -183,13 +195,17 @@ public:
|
||||
class VulkanCommands {
|
||||
public:
|
||||
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VulkanContext* context, VulkanResourceAllocator* allocator);
|
||||
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context);
|
||||
|
||||
void terminate();
|
||||
|
||||
// Creates a "current" command buffer if none exists, otherwise returns the current one.
|
||||
VulkanCommandBuffer& get();
|
||||
|
||||
// Creates a "current" protected capable command buffer if none exists, otherwise
|
||||
// returns the current one.
|
||||
VulkanCommandBuffer& getProtected();
|
||||
|
||||
// Submits the current command buffer if it exists, then sets "current" to null.
|
||||
// If there are no outstanding commands then nothing happens and this returns false.
|
||||
bool flush();
|
||||
@@ -197,11 +213,17 @@ public:
|
||||
// Returns the "rendering finished" semaphore for the most recent flush and removes
|
||||
// it from the existing dependency chain. This is especially useful for setting up
|
||||
// vkQueuePresentKHR.
|
||||
VkSemaphore acquireFinishedSignal();
|
||||
VkSemaphore acquireFinishedSignal() {
|
||||
VkSemaphore ret= mLastSubmit;
|
||||
mLastSubmit = VK_NULL_HANDLE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Takes a semaphore that signals when the next flush can occur. Only one injected
|
||||
// semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR.
|
||||
void injectDependency(VkSemaphore next);
|
||||
void injectDependency(VkSemaphore next) {
|
||||
mInjectedDependency = next;
|
||||
}
|
||||
|
||||
// Destroys all command buffers that are no longer in use.
|
||||
void gc();
|
||||
@@ -212,42 +234,25 @@ public:
|
||||
// Updates the atomic "status" variable in every extant fence.
|
||||
void updateFences();
|
||||
|
||||
// Sets an observer who is notified every time a new command buffer has been made "current".
|
||||
// The observer's event handler can only be called during get().
|
||||
void setObserver(CommandBufferObserver* observer) { mObserver = observer; }
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {});
|
||||
|
||||
void popGroupMarker();
|
||||
|
||||
void insertEventMarker(char const* string, uint32_t len);
|
||||
|
||||
std::string getTopGroupMarker() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
|
||||
VkDevice const mDevice;
|
||||
VkQueue const mQueue;
|
||||
VkCommandPool const mPool;
|
||||
VulkanContext const* mContext;
|
||||
VkQueue const mProtectedQueue;
|
||||
// For defered initialization if/when we need protected content
|
||||
uint32_t const mProtectedQueueFamilyIndex;
|
||||
VulkanContext* mContext;
|
||||
|
||||
// int8 only goes up to 127, therefore capacity must be less than that.
|
||||
static_assert(CAPACITY < 128);
|
||||
int8_t mCurrentCommandBufferIndex = -1;
|
||||
VkSemaphore mSubmissionSignal = {};
|
||||
VkSemaphore mInjectedSignal = {};
|
||||
utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>> mStorage;
|
||||
VkFence mFences[CAPACITY] = {};
|
||||
VkSemaphore mSubmissionSignals[CAPACITY] = {};
|
||||
uint8_t mAvailableBufferCount = CAPACITY;
|
||||
CommandBufferObserver* mObserver = nullptr;
|
||||
std::unique_ptr<CommandBufferPool> mPool;
|
||||
std::unique_ptr<CommandBufferPool> mProtectedPool;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
|
||||
std::unique_ptr<VulkanGroupMarkers> mCarriedOverMarkers;
|
||||
#endif
|
||||
VkSemaphore mInjectedDependency = VK_NULL_HANDLE;
|
||||
VkSemaphore mLastSubmit = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
@@ -63,7 +63,7 @@
|
||||
#define FVK_DEBUG_SHADER_MODULE 0x00000800
|
||||
#define FVK_DEBUG_READ_PIXELS 0x00001000
|
||||
#define FVK_DEBUG_PIPELINE_CACHE 0x00002000
|
||||
#define FVK_DEBUG_ALLOCATION 0x00004000
|
||||
#define FVK_DEBUG_STAGING_ALLOCATION 0x00004000
|
||||
|
||||
// Enable the debug utils extension if it is available.
|
||||
#define FVK_DEBUG_DEBUG_UTILS 0x00008000
|
||||
@@ -77,8 +77,12 @@
|
||||
// order of calls).
|
||||
#define FVK_DEBUG_FORCE_LOG_TO_I 0x00020000
|
||||
|
||||
// Enable a minimal set of traces to assess the performance of the backend.
|
||||
// All other debug features must be disabled.
|
||||
#define FVK_DEBUG_PROFILING 0x00040000
|
||||
|
||||
// Useful default combinations
|
||||
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
|
||||
#define FVK_DEBUG_EVERYTHING (0xFFFFFFFF & ~FVK_DEBUG_PROFILING)
|
||||
#define FVK_DEBUG_PERFORMANCE \
|
||||
FVK_DEBUG_SYSTRACE
|
||||
|
||||
@@ -94,6 +98,12 @@
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#endif
|
||||
|
||||
// Override the debug flags if we are forcing profiling mode
|
||||
#if defined(FILAMENT_FORCE_PROFILING_MODE)
|
||||
#undef FVK_DEBUG_FLAGS
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PROFILING)
|
||||
#endif
|
||||
|
||||
#define FVK_ENABLED(flags) (((FVK_DEBUG_FLAGS) & (flags)) == (flags))
|
||||
|
||||
// Group marker only works only if validation or debug utils is enabled since it uses
|
||||
@@ -112,6 +122,10 @@ static_assert(FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS));
|
||||
static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
#endif
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_PROFILING) && FVK_DEBUG_FLAGS != FVK_DEBUG_PROFILING
|
||||
#error PROFILING is exclusive; all other debug features must be disabled.
|
||||
#endif
|
||||
|
||||
// end dependcy checks
|
||||
|
||||
// Shorthand for combination of enabled debug flags
|
||||
@@ -123,17 +137,34 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
|
||||
// end shorthands
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
||||
#if FVK_DEBUG_FLAGS == FVK_DEBUG_PROFILING
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
#ifndef NDEBUG
|
||||
#error PROFILING is meaningless in DEBUG mode.
|
||||
#endif
|
||||
|
||||
#define FVK_SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker)
|
||||
#define FVK_SYSTRACE_END()
|
||||
#define FVK_SYSTRACE_SCOPE()
|
||||
#define FVK_PROFILE_MARKER(marker) PROFILE_SCOPE(marker)
|
||||
|
||||
#elif FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
|
||||
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
|
||||
#define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__)
|
||||
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
|
||||
|
||||
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
|
||||
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
|
||||
#else
|
||||
#define FVK_SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker)
|
||||
#define FVK_SYSTRACE_END()
|
||||
#define FVK_SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker)
|
||||
#define FVK_SYSTRACE_END()
|
||||
#define FVK_SYSTRACE_SCOPE()
|
||||
#define FVK_PROFILE_MARKER(marker)
|
||||
#endif
|
||||
|
||||
#ifndef FVK_HANDLE_ARENA_SIZE_IN_MB
|
||||
@@ -165,14 +196,16 @@ constexpr static const int FVK_REQUIRED_VERSION_MINOR = 1;
|
||||
// buffers that have been submitted but have not yet finished rendering. Note that Filament can
|
||||
// issue multiple commit calls in a single frame, and that we use a triple buffered swap chain on
|
||||
// some platforms.
|
||||
constexpr static const int FVK_MAX_COMMAND_BUFFERS = 10;
|
||||
//
|
||||
// Heuristic: Triple Buffering (3) multiplied by maximum number of renderpasses (15).
|
||||
constexpr static const int FVK_MAX_COMMAND_BUFFERS = 3 * 15;
|
||||
|
||||
// Number of command buffer submissions that should occur before an unused pipeline is removed
|
||||
// from the cache.
|
||||
//
|
||||
// If this number is low, VkPipeline construction will occur frequently, which can
|
||||
// be extremely slow. If this number is high, the memory footprint will be large.
|
||||
constexpr static const int FVK_MAX_PIPELINE_AGE = 10;
|
||||
constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
// VulkanPipelineCache does not track which command buffers contain references to which pipelines,
|
||||
// instead it simply waits for at least FVK_MAX_COMMAND_BUFFERS submissions to occur before
|
||||
|
||||
@@ -25,15 +25,11 @@
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <algorithm> // for std::max
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
using utils::FixedCapacityVector;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
} // end anonymous namespace
|
||||
@@ -86,7 +82,7 @@ VulkanTimestamps::VulkanTimestamps(VkDevice device) : mDevice(device) {
|
||||
VkQueryPoolCreateInfo tqpCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
||||
.queryType = VK_QUERY_TYPE_TIMESTAMP,
|
||||
};
|
||||
};
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
tqpCreateInfo.queryCount = mUsed.size() * 2;
|
||||
VkResult result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mPool);
|
||||
@@ -101,7 +97,7 @@ std::tuple<uint32_t, uint32_t> VulkanTimestamps::getNextQuery() {
|
||||
for (size_t timerIndex = 0; timerIndex < maxTimers; ++timerIndex) {
|
||||
if (!mUsed.test(timerIndex)) {
|
||||
mUsed.set(timerIndex);
|
||||
return std::make_tuple(timerIndex * 2, timerIndex * 2 + 1);
|
||||
return std::make_tuple(timerIndex * 2, timerIndex * 2 + 1);
|
||||
}
|
||||
}
|
||||
FVK_LOGE << "More than " << maxTimers << " timers are not supported." << utils::io::endl;
|
||||
@@ -113,7 +109,7 @@ void VulkanTimestamps::clearQuery(uint32_t queryIndex) {
|
||||
}
|
||||
|
||||
void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery* query) {
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
|
||||
auto const cmdbuffer = commands->buffer();
|
||||
@@ -121,23 +117,24 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
|
||||
vkCmdWriteTimestamp(cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
|
||||
// We stash this because getResult might come before the query is actually processed.
|
||||
query->setFence(commands->fence);
|
||||
query->setFence(commands->getFenceStatus());
|
||||
}
|
||||
|
||||
void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery const* query) {
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
|
||||
uint32_t const index = query->getStoppingQueryIndex();
|
||||
vkCmdWriteTimestamp(commands->buffer(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
}
|
||||
|
||||
VulkanTimestamps::QueryResult VulkanTimestamps::getResult(VulkanTimerQuery const* query) {
|
||||
VulkanTimestamps::QueryResult VulkanTimestamps::getResult(
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
QueryResult result;
|
||||
size_t const dataSize = result.size() * sizeof(uint64_t);
|
||||
VkDeviceSize const stride = sizeof(uint64_t) * 2;
|
||||
VkResult vkresult =
|
||||
vkGetQueryPoolResults(mDevice, mPool, index, 2, dataSize, (void*) result.data(),
|
||||
stride, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
|
||||
vkGetQueryPoolResults(mDevice, mPool, index, 2, dataSize, (void*) result.data(), stride,
|
||||
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
|
||||
FILAMENT_CHECK_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY)
|
||||
<< "vkGetQueryPoolResults error: " << static_cast<int32_t>(vkresult);
|
||||
if (vkresult == VK_NOT_READY) {
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Mutex.h>
|
||||
@@ -41,10 +43,10 @@ struct VulkanTimerQuery;
|
||||
struct VulkanCommandBuffer;
|
||||
|
||||
struct VulkanAttachment {
|
||||
VulkanTexture* texture = nullptr;
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture;
|
||||
uint8_t level = 0;
|
||||
uint8_t layerCount = 1;
|
||||
uint16_t layer = 0;
|
||||
uint8_t layer = 0;
|
||||
|
||||
bool isDepth() const;
|
||||
VkImage getImage() const;
|
||||
@@ -70,9 +72,11 @@ public:
|
||||
std::tuple<uint32_t, uint32_t> getNextQuery();
|
||||
void clearQuery(uint32_t queryIndex);
|
||||
|
||||
void beginQuery(VulkanCommandBuffer const* commands, VulkanTimerQuery* query);
|
||||
void endQuery(VulkanCommandBuffer const* commands, VulkanTimerQuery const* query);
|
||||
QueryResult getResult(VulkanTimerQuery const* query);
|
||||
void beginQuery(VulkanCommandBuffer const* commands,
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query);
|
||||
void endQuery(VulkanCommandBuffer const* commands,
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query);
|
||||
QueryResult getResult(fvkmemory::resource_ptr<VulkanTimerQuery> query);
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
@@ -82,7 +86,9 @@ private:
|
||||
};
|
||||
|
||||
struct VulkanRenderPass {
|
||||
VulkanRenderTarget* renderTarget;
|
||||
// Between the begin and end command render pass we cache the command buffer
|
||||
VulkanCommandBuffer* commandBuffer;
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget;
|
||||
VkRenderPass renderPass;
|
||||
RenderPassParams params;
|
||||
int currentSubpass;
|
||||
@@ -93,6 +99,9 @@ struct VulkanRenderPass {
|
||||
struct VulkanContext {
|
||||
public:
|
||||
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
|
||||
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
|
||||
assert_invariant(isProtectedMemorySupported() == true);
|
||||
}
|
||||
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
||||
if (flags & 1) {
|
||||
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
|
||||
@@ -113,19 +122,19 @@ public:
|
||||
}
|
||||
|
||||
inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept {
|
||||
return mPhysicalDeviceProperties.limits;
|
||||
return mPhysicalDeviceProperties.properties.limits;
|
||||
}
|
||||
|
||||
inline uint32_t getPhysicalDeviceVendorId() const noexcept {
|
||||
return mPhysicalDeviceProperties.vendorID;
|
||||
return mPhysicalDeviceProperties.properties.vendorID;
|
||||
}
|
||||
|
||||
inline bool isImageCubeArraySupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
|
||||
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDepthClampSupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
|
||||
return mPhysicalDeviceFeatures.features.depthClamp == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDebugMarkersSupported() const noexcept {
|
||||
@@ -141,21 +150,30 @@ public:
|
||||
}
|
||||
|
||||
inline bool isClipDistanceSupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
|
||||
return mPhysicalDeviceFeatures.features.shaderClipDistance == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isLazilyAllocatedMemorySupported() const noexcept {
|
||||
return mLazilyAllocatedMemorySupported;
|
||||
}
|
||||
|
||||
inline bool isProtectedMemorySupported() const noexcept {
|
||||
return mProtectedMemorySupported;
|
||||
}
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
VkPhysicalDeviceProperties mPhysicalDeviceProperties = {};
|
||||
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures = {};
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2
|
||||
};
|
||||
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2
|
||||
};
|
||||
bool mDebugMarkersSupported = false;
|
||||
bool mDebugUtilsSupported = false;
|
||||
bool mMultiviewEnabled = false;
|
||||
bool mLazilyAllocatedMemorySupported = false;
|
||||
bool mProtectedMemorySupported = false;
|
||||
|
||||
VkFormatList mDepthStencilFormats;
|
||||
VkFormatList mBlittableDepthStencilFormats;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,13 +24,14 @@
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanReadPixels.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "backend/DriverEnums.h"
|
||||
#include "caching/VulkanDescriptorSetManager.h"
|
||||
#include "caching/VulkanPipelineLayoutCache.h"
|
||||
#include "memory/ResourceManager.h"
|
||||
#include "memory/ResourcePointer.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include "private/backend/Driver.h"
|
||||
@@ -77,6 +78,9 @@ public:
|
||||
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
private:
|
||||
template<typename D>
|
||||
using resource_ptr = fvkmemory::resource_ptr<D>;
|
||||
|
||||
static constexpr uint8_t MAX_SAMPLER_BINDING_COUNT = Program::SAMPLER_BINDING_COUNT;
|
||||
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||
@@ -113,21 +117,16 @@ private:
|
||||
void collectGarbage();
|
||||
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
fvkmemory::ResourceManager mResourceManager;
|
||||
std::unique_ptr<VulkanTimestamps> mTimestamps;
|
||||
|
||||
VulkanSwapChain* mCurrentSwapChain = nullptr;
|
||||
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
|
||||
resource_ptr<VulkanSwapChain> mCurrentSwapChain;
|
||||
resource_ptr<VulkanRenderTarget> mDefaultRenderTarget;
|
||||
VulkanRenderPass mCurrentRenderPass = {};
|
||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
||||
|
||||
VulkanContext mContext = {};
|
||||
VulkanResourceAllocator mResourceAllocator;
|
||||
VulkanResourceManager mResourceManager;
|
||||
|
||||
// Used for resources that are created synchronously and used and destroyed on the backend
|
||||
// thread.
|
||||
VulkanThreadSafeResourceManager mThreadSafeResourceManager;
|
||||
|
||||
VulkanCommands mCommands;
|
||||
VulkanPipelineLayoutCache mPipelineLayoutCache;
|
||||
@@ -142,7 +141,7 @@ private:
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
struct {
|
||||
VulkanProgram* program;
|
||||
resource_ptr<VulkanProgram> program;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
DescriptorSetMask descriptorSetMask;
|
||||
} mBoundPipeline = {};
|
||||
|
||||
@@ -69,8 +69,8 @@ VulkanFboCache::~VulkanFboCache() {
|
||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||
}
|
||||
|
||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
|
||||
auto iter = mFramebufferCache.find(config);
|
||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
||||
FboMap::iterator iter = mFramebufferCache.find(config);
|
||||
if (UTILS_LIKELY(iter != mFramebufferCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
||||
iter.value().timestamp = mCurrentTime;
|
||||
return iter->second.handle;
|
||||
@@ -121,7 +121,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept {
|
||||
auto iter = mRenderPassCache.find(config);
|
||||
if (UTILS_LIKELY(iter != mRenderPassCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
||||
iter.value().timestamp = mCurrentTime;
|
||||
@@ -362,7 +362,7 @@ void VulkanFboCache::gc() noexcept {
|
||||
}
|
||||
const uint32_t evictTime = mCurrentTime - TIME_BEFORE_EVICTION;
|
||||
|
||||
for (auto iter = mFramebufferCache.begin(); iter != mFramebufferCache.end(); ++iter) {
|
||||
for (FboMap::iterator iter = mFramebufferCache.begin(); iter != mFramebufferCache.end(); ++iter) {
|
||||
const FboVal fbo = iter->second;
|
||||
if (fbo.timestamp < evictTime && fbo.handle) {
|
||||
mRenderPassRefCount[iter->first.renderPass]--;
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
uint8_t usesLazilyAllocatedMemory; // 1 byte
|
||||
uint8_t subpassMask; // 1 byte
|
||||
uint8_t viewCount; // 1 byte
|
||||
uint8_t padding[2];
|
||||
uint8_t padding[2];
|
||||
};
|
||||
struct RenderPassVal {
|
||||
VkRenderPass handle;
|
||||
@@ -98,10 +98,10 @@ public:
|
||||
~VulkanFboCache();
|
||||
|
||||
// Retrieves or creates a VkFramebuffer handle.
|
||||
VkFramebuffer getFramebuffer(FboKey config) noexcept;
|
||||
VkFramebuffer getFramebuffer(FboKey const& config) noexcept;
|
||||
|
||||
// Retrieves or creates a VkRenderPass handle.
|
||||
VkRenderPass getRenderPass(RenderPassKey config) noexcept;
|
||||
VkRenderPass getRenderPass(RenderPassKey const& config) noexcept;
|
||||
|
||||
// Evicts old unused Vulkan objects. Call this once per frame.
|
||||
void gc() noexcept;
|
||||
@@ -111,7 +111,9 @@ public:
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
tsl::robin_map<FboKey, FboVal, FboKeyHashFn, FboKeyEqualFn> mFramebufferCache;
|
||||
using FboMap = tsl::robin_map<FboKey, FboVal, FboKeyHashFn, FboKeyEqualFn>;
|
||||
FboMap mFramebufferCache;
|
||||
|
||||
tsl::robin_map<RenderPassKey, RenderPassVal, RenderPassHash, RenderPassEq> mRenderPassCache;
|
||||
tsl::robin_map<VkRenderPass, uint32_t> mRenderPassRefCount;
|
||||
uint32_t mCurrentTime = 0;
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
#include "VulkanDriver.h"
|
||||
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "spirv/VulkanSpirvUtils.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
@@ -36,15 +36,12 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) {
|
||||
rect->offset.y = framebufferHeight - rect->offset.y - rect->extent.height;
|
||||
}
|
||||
|
||||
void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
|
||||
rect->y = framebufferHeight - rect->y - rect->height;
|
||||
}
|
||||
|
||||
void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
|
||||
rect->offset.y = fbHeight - rect->offset.y - rect->extent.height;
|
||||
int32_t x = std::max(rect->offset.x, 0);
|
||||
int32_t y = std::max(rect->offset.y, 0);
|
||||
int32_t right = std::min(rect->offset.x + (int32_t) rect->extent.width, (int32_t) fbWidth);
|
||||
@@ -90,6 +87,8 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
// TODO: properly handle external sampler
|
||||
case DescriptorType::SAMPLER_EXTERNAL:
|
||||
case DescriptorType::SAMPLER: {
|
||||
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
|
||||
break;
|
||||
@@ -106,21 +105,41 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanTexture> initMsaaTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkDevice device,
|
||||
VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator,
|
||||
VulkanCommands* commands, fvkmemory::ResourceManager* resManager, uint8_t levels,
|
||||
uint8_t samples, VulkanStagePool& stagePool) {
|
||||
assert_invariant(texture);
|
||||
auto msTexture = texture->getSidecar();
|
||||
if (UTILS_UNLIKELY(!msTexture)) {
|
||||
// Clear all usage flags that are not related to attachments, so that we can
|
||||
// use the transient usage flag.
|
||||
const TextureUsage usage = texture->usage & TextureUsage::ALL_ATTACHMENTS;
|
||||
assert_invariant(static_cast<uint16_t>(usage) != 0U);
|
||||
|
||||
msTexture = resource_ptr<VulkanTexture>::construct(resManager, device, physicalDevice,
|
||||
context, allocator, resManager, commands, texture->target, levels, texture->format,
|
||||
samples, texture->width, texture->height, texture->depth, usage, stagePool);
|
||||
texture->setSidecar(msTexture);
|
||||
}
|
||||
return msTexture;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanTexture> texture) {
|
||||
mResources.push_back(texture);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanBufferObject> obj) {
|
||||
mResources.push_back(obj);
|
||||
}
|
||||
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
|
||||
bitmask(fromBackendLayout(layout)),
|
||||
: bitmask(fromBackendLayout(layout)),
|
||||
count(Count::fromLayoutBitmask(bitmask)) {}
|
||||
|
||||
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
|
||||
mResources.acquire(texture);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
|
||||
mResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
|
||||
mRangeCount = 0;
|
||||
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
|
||||
@@ -144,9 +163,9 @@ PushConstantDescription::PushConstantDescription(backend::Program const& program
|
||||
}
|
||||
}
|
||||
|
||||
void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
void PushConstantDescription::write(VkCommandBuffer cmdbuf, VkPipelineLayout layout,
|
||||
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
|
||||
VulkanCommandBuffer* cmdbuf = &(commands->get());
|
||||
|
||||
uint32_t binaryValue = 0;
|
||||
UTILS_UNUSED_IN_RELEASE auto const& types = mTypes[(uint8_t) stage];
|
||||
if (std::holds_alternative<bool>(value)) {
|
||||
@@ -162,13 +181,12 @@ void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout l
|
||||
int const ival = std::get<int>(value);
|
||||
binaryValue = *reinterpret_cast<uint32_t const*>(&ival);
|
||||
}
|
||||
vkCmdPushConstants(cmdbuf->buffer(), layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE,
|
||||
vkCmdPushConstants(cmdbuf, layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE,
|
||||
&binaryValue);
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
: HwProgram(builder.getName()),
|
||||
VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new(std::nothrow) PipelineInfo(builder)),
|
||||
mDevice(device) {
|
||||
|
||||
@@ -234,158 +252,239 @@ VulkanProgram::~VulkanProgram() {
|
||||
}
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
VulkanRenderTarget::VulkanRenderTarget() :
|
||||
HwRenderTarget(0, 0),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(false), mSamples(1) {}
|
||||
VulkanRenderTarget::VulkanRenderTarget()
|
||||
: HwRenderTarget(0, 0),
|
||||
mOffscreen(false),
|
||||
mProtected(false),
|
||||
mInfo(std::make_unique<Auxiliary>()) {
|
||||
mInfo->rpkey.samples = mInfo->fbkey.samples = 1;
|
||||
}
|
||||
|
||||
void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
|
||||
VulkanRenderTarget::~VulkanRenderTarget() = default;
|
||||
|
||||
void VulkanRenderTarget::bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain) {
|
||||
assert_invariant(!mOffscreen);
|
||||
VkExtent2D const extent = swapChain.getExtent();
|
||||
mColor[0] = { .texture = swapChain.getCurrentColor() };
|
||||
mDepth = { .texture = swapChain.getDepth() };
|
||||
|
||||
VkExtent2D const extent = swapchain->getExtent();
|
||||
width = extent.width;
|
||||
height = extent.height;
|
||||
mProtected = swapchain->isProtected();
|
||||
|
||||
VulkanAttachment color = {};
|
||||
color.texture = swapchain->getCurrentColor();
|
||||
mInfo->attachments = {color};
|
||||
|
||||
auto& fbkey = mInfo->fbkey;
|
||||
auto& rpkey = mInfo->rpkey;
|
||||
|
||||
rpkey.colorFormat[0] = color.getFormat();
|
||||
fbkey.width = width;
|
||||
fbkey.height = height;
|
||||
fbkey.color[0] = color.getImageView();
|
||||
fbkey.resolve[0] = VK_NULL_HANDLE;
|
||||
|
||||
if (swapchain->getDepth()) {
|
||||
VulkanAttachment depth = {};
|
||||
depth.texture = swapchain->getDepth();
|
||||
mInfo->attachments.push_back(depth);
|
||||
mInfo->depthIndex = 1;
|
||||
|
||||
rpkey.depthFormat = depth.getFormat();
|
||||
fbkey.depth = depth.getImageView();
|
||||
} else {
|
||||
rpkey.depthFormat = VK_FORMAT_UNDEFINED;
|
||||
fbkey.depth = VK_NULL_HANDLE;
|
||||
}
|
||||
mInfo->colors.set(0);
|
||||
}
|
||||
|
||||
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
uint32_t width, uint32_t height, uint8_t samples,
|
||||
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanContext const& context, fvkmemory::ResourceManager* resourceManager,
|
||||
VmaAllocator allocator, VulkanCommands* commands, uint32_t width, uint32_t height,
|
||||
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
|
||||
: HwRenderTarget(width, height),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(true),
|
||||
mSamples(samples),
|
||||
mLayerCount(layerCount) {
|
||||
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||
mColor[index] = color[index];
|
||||
}
|
||||
mDepth = depthStencil[0];
|
||||
VulkanTexture* depthTexture = (VulkanTexture*) mDepth.texture;
|
||||
|
||||
if (samples == 1) {
|
||||
return;
|
||||
}
|
||||
mProtected(false),
|
||||
mInfo(std::make_unique<Auxiliary>()) {
|
||||
auto& depth = depthStencil[0];
|
||||
|
||||
// Constrain the sample count according to both kinds of sample count masks obtained from
|
||||
// VkPhysicalDeviceProperties. This is consistent with the VulkanTexture constructor.
|
||||
auto const& limits = context.getPhysicalDeviceLimits();
|
||||
mSamples = samples = reduceSampleCount(samples, limits.framebufferDepthSampleCounts &
|
||||
samples = reduceSampleCount(samples, limits.framebufferDepthSampleCounts &
|
||||
limits.framebufferColorSampleCounts);
|
||||
|
||||
// Create sidecar MSAA textures for color attachments if they don't already exist.
|
||||
auto& rpkey = mInfo->rpkey;
|
||||
rpkey.samples = samples;
|
||||
rpkey.depthFormat = depth.getFormat();
|
||||
rpkey.viewCount = layerCount;
|
||||
|
||||
auto& fbkey = mInfo->fbkey;
|
||||
fbkey.width = width;
|
||||
fbkey.height = height;
|
||||
fbkey.samples = samples;
|
||||
|
||||
std::vector<VulkanAttachment>& attachments = mInfo->attachments;
|
||||
std::vector<VulkanAttachment> msaa;
|
||||
|
||||
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||
VulkanAttachment const& spec = color[index];
|
||||
VulkanTexture* texture = (VulkanTexture*) spec.texture;
|
||||
if (texture && texture->samples == 1) {
|
||||
auto msTexture = texture->getSidecar();
|
||||
if (UTILS_UNLIKELY(!msTexture)) {
|
||||
// Clear all usage flags that are not related to attachments, so that we can
|
||||
// use the transient usage flag.
|
||||
const TextureUsage usage = texture->usage & TextureUsage::ALL_ATTACHMENTS;
|
||||
assert_invariant(static_cast<uint16_t>(usage) != 0U);
|
||||
VulkanAttachment& attachment = color[index];
|
||||
auto texture = attachment.texture;
|
||||
if (!texture) {
|
||||
rpkey.colorFormat[index] = VK_FORMAT_UNDEFINED;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: This should be allocated with the ResourceAllocator.
|
||||
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
|
||||
handleAllocator,
|
||||
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
|
||||
samples, texture->width, texture->height, texture->depth, usage,
|
||||
stagePool, true /* heap allocated */);
|
||||
texture->setSidecar(msTexture);
|
||||
mProtected |= texture->getIsProtected();
|
||||
|
||||
attachments.push_back(attachment);
|
||||
mInfo->colors.set(index);
|
||||
|
||||
rpkey.colorFormat[index] = attachment.getFormat();
|
||||
fbkey.color[index] = attachment.getImageView();
|
||||
fbkey.resolve[index] = VK_NULL_HANDLE;
|
||||
|
||||
if (samples > 1) {
|
||||
VulkanAttachment msaaAttachment = {};
|
||||
if (texture->samples == 1) {
|
||||
auto msaaTexture = initMsaaTexture(texture, device, physicalDevice, context,
|
||||
allocator, commands, resourceManager, texture->levels, samples, stagePool);
|
||||
if (msaaTexture && msaaTexture->isTransientAttachment()) {
|
||||
rpkey.usesLazilyAllocatedMemory |= (1 << index);
|
||||
}
|
||||
if (attachment.texture->samples == 1) {
|
||||
rpkey.needsResolveMask |= (1 << index);
|
||||
}
|
||||
msaaAttachment = {
|
||||
.texture = msaaTexture,
|
||||
.layerCount = layerCount,
|
||||
};
|
||||
|
||||
fbkey.resolve[index] = attachment.getImageView();
|
||||
} else {
|
||||
msaaAttachment = {
|
||||
.texture = texture,
|
||||
.layerCount = layerCount,
|
||||
};
|
||||
}
|
||||
mMsaaAttachments[index] = {.texture = msTexture};
|
||||
}
|
||||
if (texture && texture->samples > 1) {
|
||||
mMsaaAttachments[index] = mColor[index];
|
||||
fbkey.color[index] = msaaAttachment.getImageView();
|
||||
msaa.push_back(msaaAttachment);
|
||||
}
|
||||
}
|
||||
|
||||
if (!depthTexture) {
|
||||
return;
|
||||
if (attachments.size() > 0 && samples > 1 && msaa.size() > 0) {
|
||||
mInfo->msaaIndex = (uint8_t) attachments.size();
|
||||
attachments.insert(attachments.end(), msaa.begin(), msaa.end());
|
||||
}
|
||||
|
||||
// There is no need for sidecar depth if the depth texture is already MSAA.
|
||||
if (depthTexture->samples > 1) {
|
||||
mMsaaDepthAttachment = mDepth;
|
||||
return;
|
||||
if (depth.texture) {
|
||||
auto depthTexture = depth.texture;
|
||||
mInfo->depthIndex = (uint8_t) attachments.size();
|
||||
attachments.push_back(depth);
|
||||
fbkey.depth = depth.getImageView();
|
||||
if (samples > 1) {
|
||||
mInfo->msaaDepthIndex = mInfo->depthIndex;
|
||||
if (depthTexture->samples == 1) {
|
||||
// MSAA depth texture must have the mipmap count of 1
|
||||
uint8_t const msLevel = 1;
|
||||
// Create sidecar MSAA texture for the depth attachment if it does not already
|
||||
// exist.
|
||||
auto msaa = initMsaaTexture(depthTexture, device, physicalDevice, context,
|
||||
allocator, commands, resourceManager, msLevel, samples, stagePool);
|
||||
mInfo->msaaDepthIndex = (uint8_t) attachments.size();
|
||||
attachments.push_back({ .texture = msaa, .layerCount = layerCount });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MSAA depth texture must have the mipmap count of 1
|
||||
uint8_t const msLevel = 1;
|
||||
|
||||
// Create sidecar MSAA texture for the depth attachment if it does not already exist.
|
||||
VulkanTexture* msTexture = depthTexture->getSidecar();
|
||||
if (UTILS_UNLIKELY(!msTexture)) {
|
||||
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
||||
commands, handleAllocator,
|
||||
depthTexture->target, msLevel, depthTexture->format, samples,
|
||||
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
|
||||
stagePool, true /* heap allocated */);
|
||||
depthTexture->setSidecar(msTexture);
|
||||
}
|
||||
|
||||
mMsaaDepthAttachment = {
|
||||
.texture = msTexture,
|
||||
.level = msLevel,
|
||||
.layer = mDepth.layer,
|
||||
};
|
||||
}
|
||||
|
||||
void VulkanRenderTarget::transformClientRectToPlatform(VkRect2D* bounds) const {
|
||||
const auto& extent = getExtent();
|
||||
flipVertically(bounds, extent.height);
|
||||
auto const& extent = getExtent();
|
||||
clampToFramebuffer(bounds, extent.width, extent.height);
|
||||
}
|
||||
|
||||
void VulkanRenderTarget::transformClientRectToPlatform(VkViewport* bounds) const {
|
||||
void VulkanRenderTarget::transformViewportToPlatform(VkViewport* bounds) const {
|
||||
flipVertically(bounds, getExtent().height);
|
||||
}
|
||||
|
||||
VkExtent2D VulkanRenderTarget::getExtent() const {
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
VulkanAttachment& VulkanRenderTarget::getColor(int target) {
|
||||
return mColor[target];
|
||||
}
|
||||
|
||||
VulkanAttachment& VulkanRenderTarget::getMsaaColor(int target) {
|
||||
return mMsaaAttachments[target];
|
||||
}
|
||||
|
||||
VulkanAttachment& VulkanRenderTarget::getDepth() {
|
||||
return mDepth;
|
||||
}
|
||||
|
||||
VulkanAttachment& VulkanRenderTarget::getMsaaDepth() {
|
||||
return mMsaaDepthAttachment;
|
||||
}
|
||||
|
||||
uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) const {
|
||||
if (!mOffscreen) {
|
||||
return 1;
|
||||
}
|
||||
if (pass.currentSubpass == 1) {
|
||||
return mInfo->colors.count();
|
||||
}
|
||||
uint8_t count = 0;
|
||||
for (uint8_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (!mColor[i].texture) {
|
||||
continue;
|
||||
}
|
||||
// NOTE: This must be consistent with VkRenderPass construction (see VulkanFboCache).
|
||||
if (!(pass.params.subpassMask & (1 << i)) || pass.currentSubpass == 1) {
|
||||
mInfo->colors.forEachSetBit([&count, &pass](size_t index) {
|
||||
if (!(pass.params.subpassMask & (1 << index))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
void VulkanRenderTarget::emitBarriersBeginRenderPass(VulkanCommandBuffer& commands) {
|
||||
auto& attachments = mInfo->attachments;
|
||||
auto samples = mInfo->fbkey.samples;
|
||||
auto barrier = [&commands](VulkanAttachment& attachment, VulkanLayout const layout) {
|
||||
auto tex = attachment.texture;
|
||||
auto const& range = attachment.getSubresourceRange();
|
||||
if (tex->getLayout(range.baseMipLevel, range.baseArrayLayer) != layout &&
|
||||
!tex->transitionLayout(&commands, range, layout)) {
|
||||
// If the layout transition did not emit a barrier, we do it manually here.
|
||||
tex->samplerToAttachmentBarrier(&commands, range);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0, count = mInfo->colors.count(); i < count; ++i) {
|
||||
auto& attachment = attachments[i];
|
||||
auto tex = attachment.texture;
|
||||
if (samples == 1 || tex->samples == 1) {
|
||||
barrier(attachment, VulkanLayout::COLOR_ATTACHMENT);
|
||||
}
|
||||
}
|
||||
if (mInfo->msaaIndex != Auxiliary::UNDEFINED_INDEX) {
|
||||
for (size_t i = mInfo->msaaIndex, count = mInfo->msaaIndex + mInfo->colors.count();
|
||||
i < count; ++i) {
|
||||
barrier(attachments[i], VulkanLayout::COLOR_ATTACHMENT);
|
||||
}
|
||||
}
|
||||
if (mInfo->depthIndex != Auxiliary::UNDEFINED_INDEX) {
|
||||
barrier(attachments[mInfo->depthIndex], VulkanLayout::DEPTH_ATTACHMENT);
|
||||
}
|
||||
if (mInfo->msaaDepthIndex != Auxiliary::UNDEFINED_INDEX) {
|
||||
barrier(attachments[mInfo->msaaDepthIndex], VulkanLayout::DEPTH_ATTACHMENT);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderTarget::emitBarriersEndRenderPass(VulkanCommandBuffer& commands) {
|
||||
if (isSwapChain()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& attachment: mInfo->attachments) {
|
||||
auto const& range = attachment.getSubresourceRange();
|
||||
bool const isDepth = attachment.isDepth();
|
||||
auto texture = attachment.texture;
|
||||
if (isDepth) {
|
||||
texture->setLayout(range, VulkanFboCache::FINAL_DEPTH_ATTACHMENT_LAYOUT);
|
||||
if (!texture->transitionLayout(&commands, range, VulkanLayout::DEPTH_SAMPLER)) {
|
||||
texture->attachmentToSamplerBarrier(&commands, range);
|
||||
}
|
||||
} else {
|
||||
texture->setLayout(range, VulkanFboCache::FINAL_COLOR_ATTACHMENT_LAYOUT);
|
||||
if (!texture->transitionLayout(&commands, range, VulkanLayout::READ_WRITE)) {
|
||||
texture->attachmentToSamplerBarrier(&commands, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VulkanVertexBufferInfo::VulkanVertexBufferInfo(
|
||||
uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER_INFO),
|
||||
mInfo(attributes.size()) {
|
||||
|
||||
auto attribDesc = mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
auto bufferDesc = mInfo.mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
auto offsets = mInfo.mSoa.data<PipelineInfo::OFFSETS>();
|
||||
@@ -423,19 +522,15 @@ VulkanVertexBufferInfo::VulkanVertexBufferInfo(
|
||||
}
|
||||
|
||||
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih)
|
||||
uint32_t vertexCount, fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi)
|
||||
: HwVertexBuffer(vertexCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||
vbih(vbih),
|
||||
mBuffers(MAX_VERTEX_BUFFER_COUNT), // TODO: can we do better here?
|
||||
mResources(allocator) {
|
||||
vbi(vbi),
|
||||
// TODO: Seems a bit wasteful. can we do better here?
|
||||
mBuffers(MAX_VERTEX_BUFFER_COUNT) {
|
||||
}
|
||||
|
||||
void VulkanVertexBuffer::setBuffer(VulkanResourceAllocator const& allocator,
|
||||
VulkanBufferObject* bufferObject, uint32_t index) {
|
||||
VulkanVertexBufferInfo const* const vbi =
|
||||
const_cast<VulkanResourceAllocator&>(allocator).handle_cast<VulkanVertexBufferInfo*>(vbih);
|
||||
void VulkanVertexBuffer::setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
|
||||
uint32_t index) {
|
||||
size_t const count = vbi->getAttributeCount();
|
||||
VkBuffer* const vkbuffers = getVkBuffers();
|
||||
int8_t const* const attribToBuffer = vbi->getAttributeToBuffer();
|
||||
@@ -444,48 +539,20 @@ void VulkanVertexBuffer::setBuffer(VulkanResourceAllocator const& allocator,
|
||||
vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer();
|
||||
}
|
||||
}
|
||||
mResources.acquire(bufferObject);
|
||||
mResources.push_back(bufferObject);
|
||||
}
|
||||
|
||||
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
uint32_t byteCount, BufferObjectBinding bindingType)
|
||||
: HwBufferObject(byteCount),
|
||||
VulkanResource(VulkanResourceType::BUFFER_OBJECT),
|
||||
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
||||
bindingType(bindingType) {}
|
||||
|
||||
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
|
||||
: VulkanThreadSafeResource(VulkanResourceType::TIMER_QUERY),
|
||||
mStartingQueryIndex(std::get<0>(indices)),
|
||||
mStoppingQueryIndex(std::get<1>(indices)) {}
|
||||
|
||||
void VulkanTimerQuery::setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mFenceMutex);
|
||||
mFence = fence;
|
||||
}
|
||||
|
||||
bool VulkanTimerQuery::isCompleted() noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mFenceMutex);
|
||||
// QueryValue is a synchronous call and might occur before beginTimerQuery has written anything
|
||||
// into the command buffer, which is an error according to the validation layer that ships in
|
||||
// the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to require that the
|
||||
// timestamp has at least been written into a processed command buffer.
|
||||
|
||||
// This fence indicates that the corresponding buffer has been completed.
|
||||
return mFence && mFence->getStatus() == VK_SUCCESS;
|
||||
}
|
||||
|
||||
VulkanTimerQuery::~VulkanTimerQuery() = default;
|
||||
|
||||
VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
|
||||
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh)
|
||||
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
|
||||
mResources(resourceAllocator) {
|
||||
type = pt;
|
||||
vertexBuffer = resourceAllocator->handle_cast<VulkanVertexBuffer*>(vbh);
|
||||
indexBuffer = resourceAllocator->handle_cast<VulkanIndexBuffer*>(ibh);
|
||||
mResources.acquire(vertexBuffer);
|
||||
mResources.acquire(indexBuffer);
|
||||
}
|
||||
VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
|
||||
fvkmemory::resource_ptr<VulkanVertexBuffer> vb,
|
||||
fvkmemory::resource_ptr<VulkanIndexBuffer> ib)
|
||||
: HwRenderPrimitive{.type = pt},
|
||||
vertexBuffer(vb),
|
||||
indexBuffer(ib) {}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
// This needs to be at the top
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanAsyncHandles.h"
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanFboCache.h"
|
||||
#include "VulkanSwapChain.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "vulkan/memory/Resource.h"
|
||||
|
||||
#include <private/backend/SamplerGroup.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
@@ -34,6 +35,8 @@
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/StructureOfArrays.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
@@ -54,7 +57,7 @@ inline uint8_t collapsedCount(Bitmask const& mask) {
|
||||
class VulkanTimestamps;
|
||||
struct VulkanBufferObject;
|
||||
|
||||
struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout {
|
||||
struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource {
|
||||
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 4;
|
||||
static constexpr uint8_t MAX_BINDINGS = 25;
|
||||
|
||||
@@ -128,17 +131,19 @@ private:
|
||||
VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
struct VulkanDescriptorSet : public VulkanResource, HwDescriptorSet {
|
||||
struct VulkanDescriptorSet : public HwDescriptorSet, fvkmemory::Resource {
|
||||
public:
|
||||
// Because we need to recycle descriptor sets not used, we allow for a callback that the "Pool"
|
||||
// can use to repackage the vk handle.
|
||||
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
||||
|
||||
VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
|
||||
VulkanDescriptorSet(VkDescriptorSet rawSet,
|
||||
UniformBufferBitmask const& dynamicUboMask,
|
||||
uint8_t uniqueDynamicUboCount,
|
||||
OnRecycle&& onRecycleFn)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
|
||||
vkSet(rawSet),
|
||||
mResources(allocator),
|
||||
: vkSet(rawSet),
|
||||
dynamicUboMask(dynamicUboMask),
|
||||
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
|
||||
~VulkanDescriptorSet() {
|
||||
@@ -147,14 +152,24 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void acquire(VulkanTexture* texture);
|
||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||
mOffsets = std::move(offsets);
|
||||
}
|
||||
|
||||
void acquire(VulkanBufferObject* texture);
|
||||
backend::DescriptorSetOffsetArray const* getOffsets() {
|
||||
return &mOffsets;
|
||||
}
|
||||
|
||||
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
|
||||
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
|
||||
|
||||
VkDescriptorSet const vkSet;
|
||||
UniformBufferBitmask const dynamicUboMask;
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
private:
|
||||
VulkanAcquireOnlyResourceManager mResources;
|
||||
backend::DescriptorSetOffsetArray mOffsets;
|
||||
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
|
||||
OnRecycle mOnRecycleFn;
|
||||
};
|
||||
|
||||
@@ -162,14 +177,11 @@ using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
|
||||
using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>;
|
||||
|
||||
struct PushConstantDescription {
|
||||
|
||||
explicit PushConstantDescription(backend::Program const& program) noexcept;
|
||||
|
||||
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
|
||||
|
||||
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
|
||||
|
||||
void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage,
|
||||
void write(VkCommandBuffer cmdbuf, VkPipelineLayout layout, backend::ShaderStage stage,
|
||||
uint8_t index, backend::PushConstantVariant const& value);
|
||||
|
||||
private:
|
||||
@@ -180,12 +192,10 @@ private:
|
||||
uint32_t mRangeCount;
|
||||
};
|
||||
|
||||
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
struct VulkanProgram : public HwProgram, fvkmemory::Resource {
|
||||
using BindingList = CappedArray<uint16_t, MAX_SAMPLER_COUNT>;
|
||||
|
||||
VulkanProgram(VkDevice device, Program const& builder) noexcept;
|
||||
|
||||
~VulkanProgram();
|
||||
|
||||
inline VkShaderModule getVertexShader() const {
|
||||
@@ -194,10 +204,6 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
|
||||
|
||||
// Get a list of the sampler binding indices so that we don't have to loop through all possible
|
||||
// samplers.
|
||||
inline BindingList const& getBindings() const { return mInfo->bindings; }
|
||||
|
||||
inline uint32_t getPushConstantRangeCount() const {
|
||||
return mInfo->pushConstantDescription.getVkRangeCount();
|
||||
}
|
||||
@@ -206,9 +212,9 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
return mInfo->pushConstantDescription.getVkRanges();
|
||||
}
|
||||
|
||||
inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
inline void writePushConstant(VkCommandBuffer cmdbuf, VkPipelineLayout layout,
|
||||
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
|
||||
mInfo->pushConstantDescription.write(commands, layout, stage, index, value);
|
||||
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
@@ -225,22 +231,10 @@ private:
|
||||
struct PipelineInfo {
|
||||
explicit PipelineInfo(backend::Program const& program) noexcept
|
||||
: pushConstantDescription(program)
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
, bindingToName(MAX_SAMPLER_COUNT, "")
|
||||
#endif
|
||||
{}
|
||||
|
||||
BindingList bindings;
|
||||
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
|
||||
|
||||
PushConstantDescription pushConstantDescription;
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
// We store the sampler name mapped from binding index (only for debug purposes).
|
||||
utils::FixedCapacityVector<std::string> bindingToName;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
@@ -255,47 +249,88 @@ private:
|
||||
//
|
||||
// We use private inheritance to shield clients from the width / height fields in HwRenderTarget,
|
||||
// which are not representative when this is the default render target.
|
||||
struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
|
||||
// Creates an offscreen render target.
|
||||
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator,
|
||||
VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
uint32_t width, uint32_t height,
|
||||
VulkanContext const& context, fvkmemory::ResourceManager* resourceManager,
|
||||
VmaAllocator allocator, VulkanCommands* commands, uint32_t width, uint32_t height,
|
||||
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount);
|
||||
|
||||
~VulkanRenderTarget();
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
explicit VulkanRenderTarget();
|
||||
|
||||
void transformClientRectToPlatform(VkRect2D* bounds) const;
|
||||
void transformClientRectToPlatform(VkViewport* bounds) const;
|
||||
VkExtent2D getExtent() const;
|
||||
// We return references in the following methods to avoid a copy.
|
||||
VulkanAttachment& getColor(int target);
|
||||
VulkanAttachment& getMsaaColor(int target);
|
||||
VulkanAttachment& getDepth();
|
||||
VulkanAttachment& getMsaaDepth();
|
||||
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
|
||||
uint8_t getSamples() const { return mSamples; }
|
||||
uint8_t getLayerCount() const { return mLayerCount; }
|
||||
bool hasDepth() const { return mDepth.texture; }
|
||||
bool isSwapChain() const { return !mOffscreen; }
|
||||
void bindToSwapChain(VulkanSwapChain& surf);
|
||||
|
||||
void transformViewportToPlatform(VkViewport* bounds) const;
|
||||
|
||||
inline VkExtent2D getExtent() const {
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
inline VulkanAttachment& getColor0() const {
|
||||
assert_invariant(mInfo->colors[0]);
|
||||
return mInfo->attachments[0];
|
||||
}
|
||||
|
||||
inline VulkanAttachment& getDepth() const {
|
||||
assert_invariant(hasDepth());
|
||||
if (mInfo->fbkey.samples == 1) {
|
||||
return mInfo->attachments[mInfo->depthIndex];
|
||||
}
|
||||
return mInfo->attachments[mInfo->msaaDepthIndex];
|
||||
}
|
||||
|
||||
inline VulkanFboCache::RenderPassKey const& getRenderPassKey() const {
|
||||
return mInfo->rpkey;
|
||||
}
|
||||
|
||||
inline VulkanFboCache::FboKey const& getFboKey() const {
|
||||
return mInfo->fbkey;
|
||||
}
|
||||
|
||||
inline uint8_t getSamples() const {
|
||||
return mInfo->fbkey.samples;
|
||||
}
|
||||
|
||||
uint8_t getColorTargetCount(VulkanRenderPass const& pass) const;
|
||||
|
||||
inline bool hasDepth() const { return mInfo->depthIndex != Auxiliary::UNDEFINED_INDEX; }
|
||||
|
||||
inline bool isSwapChain() const { return !mOffscreen; }
|
||||
inline bool isProtected() const { return mProtected; }
|
||||
|
||||
void bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain);
|
||||
|
||||
void emitBarriersBeginRenderPass(VulkanCommandBuffer& commands);
|
||||
|
||||
void emitBarriersEndRenderPass(VulkanCommandBuffer& commands);
|
||||
|
||||
private:
|
||||
VulkanAttachment mColor[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
|
||||
VulkanAttachment mDepth = {};
|
||||
VulkanAttachment mMsaaAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
|
||||
VulkanAttachment mMsaaDepthAttachment = {};
|
||||
const bool mOffscreen : 1;
|
||||
uint8_t mSamples : 7;
|
||||
uint8_t mLayerCount = 1;
|
||||
struct Auxiliary {
|
||||
static constexpr int8_t UNDEFINED_INDEX = -1;
|
||||
|
||||
explicit Auxiliary() noexcept = default;
|
||||
|
||||
VulkanFboCache::RenderPassKey rpkey = {};
|
||||
VulkanFboCache::FboKey fbkey = {};
|
||||
std::vector<VulkanAttachment> attachments;
|
||||
utils::bitset32 colors;
|
||||
int8_t depthIndex = UNDEFINED_INDEX;
|
||||
int8_t msaaDepthIndex = UNDEFINED_INDEX;
|
||||
int8_t msaaIndex = UNDEFINED_INDEX;
|
||||
};
|
||||
bool const mOffscreen;
|
||||
bool mProtected;
|
||||
|
||||
std::unique_ptr<Auxiliary> mInfo;
|
||||
};
|
||||
|
||||
struct VulkanBufferObject;
|
||||
|
||||
struct VulkanVertexBufferInfo : public HwVertexBufferInfo, VulkanResource {
|
||||
struct VulkanVertexBufferInfo : public HwVertexBufferInfo, fvkmemory::Resource {
|
||||
VulkanVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes);
|
||||
|
||||
@@ -342,33 +377,24 @@ private:
|
||||
PipelineInfo mInfo;
|
||||
};
|
||||
|
||||
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih);
|
||||
struct VulkanVertexBuffer : public HwVertexBuffer, fvkmemory::Resource {
|
||||
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, uint32_t vertexCount,
|
||||
fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi);
|
||||
void setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> bufferObject, uint32_t index);
|
||||
|
||||
void setBuffer(VulkanResourceAllocator const& allocator,
|
||||
VulkanBufferObject* bufferObject, uint32_t index);
|
||||
inline VkBuffer const* getVkBuffers() const { return mBuffers.data(); }
|
||||
inline VkBuffer* getVkBuffers() { return mBuffers.data(); }
|
||||
fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi;
|
||||
|
||||
inline VkBuffer const* getVkBuffers() const {
|
||||
return mBuffers.data();
|
||||
}
|
||||
|
||||
inline VkBuffer* getVkBuffers() {
|
||||
return mBuffers.data();
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
private:
|
||||
utils::FixedCapacityVector<VkBuffer> mBuffers;
|
||||
FixedSizeVulkanResourceManager<MAX_VERTEX_BUFFER_COUNT> mResources;
|
||||
std::vector<fvkmemory::resource_ptr<VulkanBufferObject>> mResources;
|
||||
};
|
||||
|
||||
struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource {
|
||||
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, uint8_t elementSize,
|
||||
uint32_t indexCount)
|
||||
: HwIndexBuffer(elementSize, indexCount),
|
||||
VulkanResource(VulkanResourceType::INDEX_BUFFER),
|
||||
buffer(allocator, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, elementSize * indexCount),
|
||||
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
|
||||
|
||||
@@ -376,7 +402,7 @@ struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
const VkIndexType indexType;
|
||||
};
|
||||
|
||||
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
|
||||
struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource {
|
||||
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType);
|
||||
|
||||
@@ -384,66 +410,15 @@ struct VulkanBufferObject : public HwBufferObject, VulkanResource {
|
||||
const BufferObjectBinding bindingType;
|
||||
};
|
||||
|
||||
struct VulkanSamplerGroup : public HwSamplerGroup, VulkanResource {
|
||||
// NOTE: we have to use out-of-line allocation here because the size of a Handle<> is limited
|
||||
std::unique_ptr<SamplerGroup> sb;// FIXME: this shouldn't depend on filament::SamplerGroup
|
||||
explicit VulkanSamplerGroup(size_t size) noexcept
|
||||
: VulkanResource(VulkanResourceType::SAMPLER_GROUP),
|
||||
sb(new SamplerGroup(size)) {}
|
||||
struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
|
||||
VulkanRenderPrimitive(PrimitiveType pt, fvkmemory::resource_ptr<VulkanVertexBuffer> vb,
|
||||
fvkmemory::resource_ptr<VulkanIndexBuffer> ib);
|
||||
~VulkanRenderPrimitive() = default;
|
||||
|
||||
fvkmemory::resource_ptr<VulkanVertexBuffer> vertexBuffer;
|
||||
fvkmemory::resource_ptr<VulkanIndexBuffer> indexBuffer;
|
||||
};
|
||||
|
||||
struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource {
|
||||
VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
|
||||
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
|
||||
|
||||
~VulkanRenderPrimitive() {
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
VulkanVertexBuffer* vertexBuffer = nullptr;
|
||||
VulkanIndexBuffer* indexBuffer = nullptr;
|
||||
|
||||
private:
|
||||
// Keep references to the vertex buffer and the index buffer.
|
||||
FixedSizeVulkanResourceManager<2> mResources;
|
||||
};
|
||||
|
||||
struct VulkanFence : public HwFence, VulkanResource {
|
||||
VulkanFence()
|
||||
: VulkanResource(VulkanResourceType::FENCE) {}
|
||||
|
||||
explicit VulkanFence(std::shared_ptr<VulkanCmdFence> fence)
|
||||
: VulkanResource(VulkanResourceType::FENCE),
|
||||
fence(fence) {}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
};
|
||||
|
||||
struct VulkanTimerQuery : public HwTimerQuery, VulkanThreadSafeResource {
|
||||
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
|
||||
~VulkanTimerQuery();
|
||||
|
||||
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept;
|
||||
|
||||
bool isCompleted() noexcept;
|
||||
|
||||
uint32_t getStartingQueryIndex() const {
|
||||
return mStartingQueryIndex;
|
||||
}
|
||||
|
||||
uint32_t getStoppingQueryIndex() const {
|
||||
return mStoppingQueryIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mStartingQueryIndex;
|
||||
uint32_t mStoppingQueryIndex;
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> mFence;
|
||||
utils::Mutex mFenceMutex;
|
||||
};
|
||||
|
||||
|
||||
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
|
||||
BufferObjectBinding bindingType) noexcept {
|
||||
switch(bindingType) {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#pragma clang diagnostic ignored "-Wnullability-completeness"
|
||||
#pragma clang diagnostic ignored "-Wextra-semi"
|
||||
#pragma clang diagnostic ignored "-Wc++98-compat-extra-semi"
|
||||
#pragma clang diagnostic ignored "-Wthread-safety-analysis"
|
||||
|
||||
static const PFN_vkGetInstanceProcAddr& vkGetInstanceProcAddr = bluevk::vkGetInstanceProcAddr;
|
||||
static const PFN_vkGetDeviceProcAddr& vkGetDeviceProcAddr = bluevk::vkGetDeviceProcAddr;
|
||||
|
||||
@@ -66,17 +66,12 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
|
||||
PipelineCacheEntry* cacheEntry = getOrCreatePipeline();
|
||||
// Check if the required pipeline is already bound.
|
||||
if (cacheEntry->handle == commands->pipeline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If an error occurred, allow higher levels to handle it gracefully.
|
||||
assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline");
|
||||
|
||||
mBoundPipeline = mPipelineRequirements;
|
||||
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
|
||||
commands->setPipeline(cacheEntry->handle);
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
|
||||
@@ -246,7 +241,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
|
||||
void VulkanPipelineCache::bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept {
|
||||
mPipelineRequirements.shaders[0] = program->getVertexShader();
|
||||
mPipelineRequirements.shaders[1] = program->getFragmentShader();
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -44,7 +43,6 @@ namespace filament::backend {
|
||||
struct VulkanProgram;
|
||||
struct VulkanBufferObject;
|
||||
struct VulkanTexture;
|
||||
class VulkanResourceAllocator;
|
||||
|
||||
// VulkanPipelineCache manages a cache of descriptor sets and pipelines.
|
||||
//
|
||||
@@ -122,7 +120,7 @@ public:
|
||||
void bindPipeline(VulkanCommandBuffer* commands);
|
||||
|
||||
// Each of the following methods are fast and do not make Vulkan calls.
|
||||
void bindProgram(VulkanProgram* program) noexcept;
|
||||
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
|
||||
void bindRasterState(const RasterState& rasterState) noexcept;
|
||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||
|
||||
@@ -117,9 +117,10 @@ void VulkanReadPixels::terminate() noexcept {
|
||||
VulkanReadPixels::VulkanReadPixels(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint32_t const y,
|
||||
uint32_t const width, uint32_t const height, uint32_t const graphicsQueueFamilyIndex,
|
||||
PixelBufferDescriptor&& pbd, SelecteMemoryFunction const& selectMemoryFunc,
|
||||
void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget, uint32_t const x,
|
||||
uint32_t const y, uint32_t const width, uint32_t const height,
|
||||
uint32_t const graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
|
||||
SelecteMemoryFunction const& selectMemoryFunc,
|
||||
OnReadCompleteFunction const& readCompleteFunc) {
|
||||
assert_invariant(mDevice != VK_NULL_HANDLE);
|
||||
|
||||
@@ -143,7 +144,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
|
||||
VkCommandPool& cmdpool = mCommandPool;
|
||||
|
||||
VulkanTexture* srcTexture = srcTarget->getColor(0).texture;
|
||||
fvkmemory::resource_ptr<VulkanTexture> srcTexture = srcTarget->getColor0().texture;
|
||||
assert_invariant(srcTexture);
|
||||
VkFormat const srcFormat = srcTexture->getVkFormat();
|
||||
bool const swizzle
|
||||
@@ -230,7 +231,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
},
|
||||
});
|
||||
|
||||
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
|
||||
VulkanAttachment const srcAttachment = srcTarget->getColor0();
|
||||
VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange();
|
||||
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
|
||||
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
@@ -72,9 +73,9 @@ public:
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
void run(VulkanRenderTarget* srcTarget, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
|
||||
uint32_t graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
|
||||
SelecteMemoryFunction const& selectMemoryFunc,
|
||||
void run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget, uint32_t x, uint32_t y,
|
||||
uint32_t width, uint32_t height, uint32_t graphicsQueueFamilyIndex,
|
||||
PixelBufferDescriptor&& pbd, SelecteMemoryFunction const& selectMemoryFunc,
|
||||
OnReadCompleteFunction const& readCompleteFunc);
|
||||
|
||||
// This method will block until all of the in-flight requests are complete.
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* 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_VULKANRESOURCEALLOCATOR_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <private/backend/HandleAllocator.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
#define RESOURCE_TYPE_COUNT (static_cast<int>(VulkanResourceType::END_TYPE))
|
||||
#define DEBUG_RESOURCE_LEAKS FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
#define TRACK_INCREMENT() \
|
||||
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]++; \
|
||||
}
|
||||
#define TRACK_DECREMENT() \
|
||||
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]--; \
|
||||
}
|
||||
#else
|
||||
// No-op
|
||||
#define TRACK_INCREMENT()
|
||||
#define TRACK_DECREMENT()
|
||||
#endif
|
||||
|
||||
class VulkanResourceAllocator {
|
||||
public:
|
||||
using AllocatorImpl = HandleAllocatorVK;
|
||||
VulkanResourceAllocator(size_t arenaSize, bool disableUseAfterFreeCheck)
|
||||
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck)
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
, mDebugOnlyResourceCount(RESOURCE_TYPE_COUNT) {
|
||||
std::memset(mDebugOnlyResourceCount.data(), 0, sizeof(size_t) * RESOURCE_TYPE_COUNT);
|
||||
}
|
||||
#else
|
||||
{}
|
||||
#endif
|
||||
|
||||
template<typename D, typename... ARGS>
|
||||
inline Handle<D> initHandle(ARGS&&... args) noexcept {
|
||||
auto handle = mHandleAllocatorImpl.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
|
||||
auto obj = handle_cast<D*>(handle);
|
||||
obj->initResource(handle.getId());
|
||||
TRACK_INCREMENT();
|
||||
return handle;
|
||||
}
|
||||
|
||||
template<typename D>
|
||||
inline Handle<D> allocHandle() noexcept {
|
||||
return mHandleAllocatorImpl.allocate<D>();
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
inline typename std::enable_if<std::is_base_of<B, D>::value, D>::type* construct(
|
||||
Handle<B> const& handle, ARGS&&... args) noexcept {
|
||||
auto obj = mHandleAllocatorImpl.construct<D, B>(handle, std::forward<ARGS>(args)...);
|
||||
obj->initResource(handle.getId());
|
||||
TRACK_INCREMENT();
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> && std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B>& handle) noexcept {
|
||||
return mHandleAllocatorImpl.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> && std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B> const& handle) noexcept {
|
||||
return mHandleAllocatorImpl.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
template<typename D, typename B>
|
||||
inline void destruct(Handle<B> handle) noexcept {
|
||||
auto obj = handle_cast<D*>(handle);
|
||||
TRACK_DECREMENT();
|
||||
mHandleAllocatorImpl.deallocate(handle, obj);
|
||||
}
|
||||
|
||||
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
|
||||
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
|
||||
}
|
||||
|
||||
private:
|
||||
AllocatorImpl mHandleAllocatorImpl;
|
||||
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
public:
|
||||
void print() {
|
||||
FVK_LOGD << "Resource Allocator state (debug only)" << utils::io::endl;
|
||||
for (size_t i = 0; i < RESOURCE_TYPE_COUNT; i++) {
|
||||
FVK_LOGD << "[" << i << "]=" << mDebugOnlyResourceCount[i] << utils::io::endl;
|
||||
}
|
||||
FVK_LOGD << "+++++++++++++++++++++++++++++++++++++" << utils::io::endl;
|
||||
}
|
||||
private:
|
||||
utils::FixedCapacityVector<size_t> mDebugOnlyResourceCount;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
#undef TRACK_INCREMENT
|
||||
#undef TRACK_DECREMENT
|
||||
#undef DEBUG_RESOURCE_LEAKS
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType type,
|
||||
HandleBase::HandleId id) {
|
||||
|
||||
if (IS_HEAP_ALLOC_TYPE(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case VulkanResourceType::BUFFER_OBJECT:
|
||||
allocator->destruct<VulkanBufferObject>(Handle<HwBufferObject>(id));
|
||||
break;
|
||||
case VulkanResourceType::INDEX_BUFFER:
|
||||
allocator->destruct<VulkanIndexBuffer>(Handle<HwIndexBuffer>(id));
|
||||
break;
|
||||
case VulkanResourceType::PROGRAM:
|
||||
allocator->destruct<VulkanProgram>(Handle<HwProgram>(id));
|
||||
break;
|
||||
case VulkanResourceType::RENDER_TARGET:
|
||||
allocator->destruct<VulkanRenderTarget>(Handle<HwRenderTarget>(id));
|
||||
break;
|
||||
case VulkanResourceType::SAMPLER_GROUP:
|
||||
allocator->destruct<VulkanSamplerGroup>(Handle<HwSamplerGroup>(id));
|
||||
break;
|
||||
case VulkanResourceType::SWAP_CHAIN:
|
||||
allocator->destruct<VulkanSwapChain>(Handle<HwSwapChain>(id));
|
||||
break;
|
||||
case VulkanResourceType::TEXTURE:
|
||||
allocator->destruct<VulkanTexture>(Handle<HwTexture>(id));
|
||||
break;
|
||||
case VulkanResourceType::TIMER_QUERY:
|
||||
allocator->destruct<VulkanTimerQuery>(Handle<HwTimerQuery>(id));
|
||||
break;
|
||||
case VulkanResourceType::VERTEX_BUFFER_INFO:
|
||||
allocator->destruct<VulkanVertexBufferInfo>(Handle<HwVertexBufferInfo>(id));
|
||||
break;
|
||||
case VulkanResourceType::VERTEX_BUFFER:
|
||||
allocator->destruct<VulkanVertexBuffer>(Handle<HwVertexBuffer>(id));
|
||||
break;
|
||||
case VulkanResourceType::RENDER_PRIMITIVE:
|
||||
allocator->destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
|
||||
break;
|
||||
case VulkanResourceType::DESCRIPTOR_SET_LAYOUT:
|
||||
allocator->destruct<VulkanDescriptorSetLayout>(Handle<VulkanDescriptorSetLayout>(id));
|
||||
break;
|
||||
case VulkanResourceType::DESCRIPTOR_SET:
|
||||
allocator->destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
|
||||
break;
|
||||
|
||||
// If the resource is heap allocated, then the resource manager just skip refcounted
|
||||
// destruction.
|
||||
case VulkanResourceType::FENCE:
|
||||
case VulkanResourceType::HEAP_ALLOCATED:
|
||||
case VulkanResourceType::END_TYPE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,313 +0,0 @@
|
||||
/*
|
||||
* 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_VULKANRESOURCES_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <tsl/robin_set.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanResourceAllocator;
|
||||
struct VulkanThreadSafeResource;
|
||||
|
||||
// Subclasses of VulkanResource must provide this enum in their construction.
|
||||
enum class VulkanResourceType : uint8_t {
|
||||
BUFFER_OBJECT = 0,
|
||||
INDEX_BUFFER = 1,
|
||||
PROGRAM = 2,
|
||||
RENDER_TARGET = 3,
|
||||
SAMPLER_GROUP = 4,
|
||||
SWAP_CHAIN = 5,
|
||||
RENDER_PRIMITIVE = 6,
|
||||
TEXTURE = 7,
|
||||
TIMER_QUERY = 8,
|
||||
VERTEX_BUFFER = 9,
|
||||
VERTEX_BUFFER_INFO = 10,
|
||||
DESCRIPTOR_SET_LAYOUT = 11,
|
||||
DESCRIPTOR_SET = 12,
|
||||
|
||||
// Below are resources that are managed manually (i.e. not ref counted).
|
||||
FENCE = 13,
|
||||
HEAP_ALLOCATED = 14,
|
||||
|
||||
END_TYPE = 15, // A placeholder
|
||||
};
|
||||
|
||||
#define IS_HEAP_ALLOC_TYPE(f) \
|
||||
(f == VulkanResourceType::FENCE || f == VulkanResourceType::HEAP_ALLOCATED)
|
||||
|
||||
|
||||
// This is a ref-counting base class that tracks how many references of this resource exist. This
|
||||
// class is paired with VulkanResourceManagerImpl which is responsible for incrementing or
|
||||
// decrementing the count. Once mRefCount == 0, VulkanResourceManagerImpl will also call the
|
||||
// appropriate destructor. VulkanCommandBuffer, VulkanDriver, and composite structure like
|
||||
// VulkanRenderPrimitive are owners of VulkanResourceManagerImpl instances.
|
||||
struct VulkanResourceBase {
|
||||
protected:
|
||||
explicit VulkanResourceBase(VulkanResourceType type)
|
||||
: mRefCount(IS_HEAP_ALLOC_TYPE(type) ? 1 : 0),
|
||||
mType(uint32_t(type)),
|
||||
mHandleId(0) {
|
||||
}
|
||||
|
||||
private:
|
||||
inline VulkanResourceType getType() const noexcept {
|
||||
return VulkanResourceType(mType);
|
||||
}
|
||||
|
||||
inline HandleBase::HandleId getId() const noexcept {
|
||||
return mHandleId;
|
||||
}
|
||||
|
||||
inline void initResource(HandleBase::HandleId id) noexcept {
|
||||
mHandleId = id;
|
||||
}
|
||||
|
||||
inline void ref() noexcept {
|
||||
if (IS_HEAP_ALLOC_TYPE(getType())) {
|
||||
return;
|
||||
}
|
||||
assert_invariant(mRefCount < ((1<<24) - 1));
|
||||
++mRefCount;
|
||||
}
|
||||
|
||||
inline void deref() noexcept {
|
||||
if (IS_HEAP_ALLOC_TYPE(getType())) {
|
||||
return;
|
||||
}
|
||||
assert_invariant(mRefCount > 0);
|
||||
--mRefCount;
|
||||
}
|
||||
|
||||
inline size_t refcount() const noexcept {
|
||||
return mRefCount;
|
||||
}
|
||||
|
||||
uint32_t mRefCount : 24; // 16M is enough for the refcount
|
||||
uint32_t mType : 8; // must be uint32_t or MSVC doesn't pack it. no codegen impact w/ clang.
|
||||
HandleBase::HandleId mHandleId;
|
||||
|
||||
friend struct VulkanThreadSafeResource;
|
||||
friend class VulkanResourceAllocator;
|
||||
|
||||
template<typename RT, typename ST>
|
||||
friend class VulkanResourceManagerImpl;
|
||||
};
|
||||
|
||||
static_assert(sizeof(VulkanResourceBase) == 8, "VulkanResourceBase should be 8 bytes");
|
||||
|
||||
struct VulkanThreadSafeResource {
|
||||
protected:
|
||||
explicit VulkanThreadSafeResource(VulkanResourceType type)
|
||||
: mImpl(type) {}
|
||||
|
||||
private:
|
||||
inline VulkanResourceType getType() {
|
||||
return mImpl.getType();
|
||||
}
|
||||
|
||||
inline HandleBase::HandleId getId() {
|
||||
return mImpl.getId();
|
||||
}
|
||||
|
||||
inline void initResource(HandleBase::HandleId id) noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
mImpl.initResource(id);
|
||||
}
|
||||
|
||||
inline void ref() noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
mImpl.ref();
|
||||
}
|
||||
|
||||
inline void deref() noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
mImpl.deref();
|
||||
}
|
||||
|
||||
inline size_t refcount() noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
return mImpl.refcount();
|
||||
}
|
||||
|
||||
utils::Mutex mMutex;
|
||||
VulkanResourceBase mImpl;
|
||||
|
||||
friend class VulkanResourceAllocator;
|
||||
template<typename RT, typename ST>
|
||||
friend class VulkanResourceManagerImpl;
|
||||
};
|
||||
|
||||
using VulkanResource = VulkanResourceBase;
|
||||
|
||||
namespace {
|
||||
|
||||
// When the size of the resource set is known to be small, (for example for VulkanRenderPrimitive),
|
||||
// we just use a std::array to back the set.
|
||||
template<std::size_t SIZE>
|
||||
using FixedCapacityResourceSet = CappedArray<VulkanResource*, SIZE>;
|
||||
|
||||
// robin_set/map are useful for sets that are acquire only and the set will be iterated when the set
|
||||
// is cleared.
|
||||
using FastIterationResourceSet = tsl::robin_set<VulkanResource*>;
|
||||
|
||||
// unoredered_set is used in the general case where insert/erase can occur at will. This is useful
|
||||
// for the basic object ownership count - i.e. VulkanDriver.
|
||||
using ResourceSet = std::unordered_set<VulkanResource*>;
|
||||
|
||||
using ThreadSafeResourceSet = std::unordered_set<VulkanThreadSafeResource*>;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class VulkanResourceAllocator;
|
||||
|
||||
#define LOCK_IF_NEEDED() \
|
||||
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) { \
|
||||
mMutex->lock(); \
|
||||
}
|
||||
|
||||
#define UNLOCK_IF_NEEDED() \
|
||||
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) { \
|
||||
mMutex->unlock(); \
|
||||
}
|
||||
|
||||
void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType type,
|
||||
HandleBase::HandleId id);
|
||||
|
||||
template<typename ResourceType, typename SetType>
|
||||
class VulkanResourceManagerImpl {
|
||||
public:
|
||||
explicit VulkanResourceManagerImpl(VulkanResourceAllocator* allocator)
|
||||
: mAllocator(allocator) {
|
||||
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) {
|
||||
mMutex = std::make_unique<utils::Mutex>();
|
||||
}
|
||||
}
|
||||
|
||||
VulkanResourceManagerImpl(const VulkanResourceManagerImpl& other) = delete;
|
||||
void operator=(const VulkanResourceManagerImpl& other) = delete;
|
||||
VulkanResourceManagerImpl(const VulkanResourceManagerImpl&& other) = delete;
|
||||
void operator=(const VulkanResourceManagerImpl&& other) = delete;
|
||||
|
||||
~VulkanResourceManagerImpl() {
|
||||
clear();
|
||||
}
|
||||
|
||||
inline void acquire(ResourceType* resource) {
|
||||
if (IS_HEAP_ALLOC_TYPE(resource->getType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOCK_IF_NEEDED();
|
||||
if (mResources.find(resource) != mResources.end()) {
|
||||
UNLOCK_IF_NEEDED();
|
||||
return;
|
||||
}
|
||||
mResources.insert(resource);
|
||||
UNLOCK_IF_NEEDED();
|
||||
resource->ref();
|
||||
}
|
||||
|
||||
// Transfers ownership from one resource set to another
|
||||
template <typename tSetType>
|
||||
inline void acquireAll(VulkanResourceManagerImpl<ResourceType, tSetType>* srcResources) {
|
||||
copyAll(srcResources);
|
||||
srcResources->clear();
|
||||
}
|
||||
|
||||
// Transfers ownership from one resource set to another
|
||||
template <typename tSetType>
|
||||
inline void copyAll(VulkanResourceManagerImpl<ResourceType, tSetType>* srcResources) {
|
||||
LOCK_IF_NEEDED();
|
||||
for (auto iter = srcResources->mResources.begin(); iter != srcResources->mResources.end();
|
||||
iter++) {
|
||||
acquire(*iter);
|
||||
}
|
||||
UNLOCK_IF_NEEDED();
|
||||
}
|
||||
|
||||
inline void release(ResourceType* resource) {
|
||||
if (IS_HEAP_ALLOC_TYPE(resource->getType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOCK_IF_NEEDED();
|
||||
auto resItr = mResources.find(resource);
|
||||
if (resItr == mResources.end()) {
|
||||
UNLOCK_IF_NEEDED();
|
||||
return;
|
||||
}
|
||||
mResources.erase(resItr);
|
||||
UNLOCK_IF_NEEDED();
|
||||
derefImpl(resource);
|
||||
}
|
||||
|
||||
inline void clear() {
|
||||
LOCK_IF_NEEDED();
|
||||
for (auto iter = mResources.begin(); iter != mResources.end(); iter++) {
|
||||
derefImpl(*iter);
|
||||
}
|
||||
mResources.clear();
|
||||
UNLOCK_IF_NEEDED();
|
||||
}
|
||||
|
||||
inline size_t size() {
|
||||
return mResources.size();
|
||||
}
|
||||
|
||||
private:
|
||||
inline void derefImpl(ResourceType* resource) {
|
||||
resource->deref();
|
||||
if (resource->refcount() != 0) {
|
||||
return;
|
||||
}
|
||||
deallocateResource(mAllocator, resource->getType(), resource->getId());
|
||||
}
|
||||
|
||||
VulkanResourceAllocator* mAllocator;
|
||||
SetType mResources;
|
||||
std::unique_ptr<utils::Mutex> mMutex;
|
||||
|
||||
template <typename, typename> friend class VulkanResourceManagerImpl;
|
||||
};
|
||||
|
||||
using VulkanAcquireOnlyResourceManager
|
||||
= VulkanResourceManagerImpl<VulkanResource, FastIterationResourceSet>;
|
||||
using VulkanResourceManager = VulkanResourceManagerImpl<VulkanResource, ResourceSet>;
|
||||
|
||||
template<std::size_t SIZE>
|
||||
using FixedSizeVulkanResourceManager =
|
||||
VulkanResourceManagerImpl<VulkanResource, FixedCapacityResourceSet<SIZE>>;
|
||||
|
||||
using VulkanThreadSafeResourceManager
|
||||
= VulkanResourceManagerImpl<VulkanThreadSafeResource, ThreadSafeResourceSet>;
|
||||
|
||||
#undef LOCK_IF_NEEDED
|
||||
#undef UNLOCK_IF_NEEDED
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "VulkanStagePool.h"
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanMemory.h"
|
||||
@@ -37,6 +38,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
if (iter != mFreeStages.end()) {
|
||||
auto stage = iter->second;
|
||||
mFreeStages.erase(iter);
|
||||
stage->lastAccessed = mCurrentFrame;
|
||||
mUsedStages.insert(stage);
|
||||
return stage;
|
||||
}
|
||||
@@ -59,7 +61,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo,
|
||||
&allocInfo, &stage->buffer, &stage->memory, nullptr);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_ALLOCATION)
|
||||
#if FVK_ENABLED(FVK_DEBUG_STAGING_ALLOCATION)
|
||||
if (result != VK_SUCCESS) {
|
||||
FVK_LOGE << "Allocation error: " << result << utils::io::endl;
|
||||
}
|
||||
@@ -74,6 +76,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
for (auto image : mFreeImages) {
|
||||
if (image->format == vkformat && image->width == width && image->height == height) {
|
||||
mFreeImages.erase(image);
|
||||
image->lastAccessed = mCurrentFrame;
|
||||
mUsedImages.insert(image);
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "backend/DriverEnums.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
#include <map>
|
||||
@@ -26,6 +25,8 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanCommands;
|
||||
|
||||
// Immutable POD representing a shared CPU-GPU staging area.
|
||||
struct VulkanStage {
|
||||
VmaAllocation memory;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
#include "VulkanSwapChain.h"
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanTexture.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
@@ -26,19 +28,18 @@ using namespace utils;
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
|
||||
VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
|
||||
VulkanStagePool& stagePool,
|
||||
void* nativeWindow, uint64_t flags, VkExtent2D extent)
|
||||
: VulkanResource(VulkanResourceType::SWAP_CHAIN),
|
||||
mPlatform(platform),
|
||||
fvkmemory::ResourceManager* resourceManager, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags,
|
||||
VkExtent2D extent)
|
||||
: mPlatform(platform),
|
||||
mResourceManager(resourceManager),
|
||||
mCommands(commands),
|
||||
mAllocator(allocator),
|
||||
mHandleAllocator(handleAllocator),
|
||||
mStagePool(stagePool),
|
||||
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
|
||||
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
|
||||
mTransitionSwapChainImageLayoutForPresent(
|
||||
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
|
||||
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
|
||||
mAcquired(false),
|
||||
mIsFirstRenderPass(true) {
|
||||
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
|
||||
@@ -53,6 +54,9 @@ VulkanSwapChain::~VulkanSwapChain() {
|
||||
mCommands->flush();
|
||||
mCommands->wait();
|
||||
|
||||
mColors = {};
|
||||
mDepth = {};
|
||||
|
||||
mPlatform->destroy(swapChain);
|
||||
}
|
||||
|
||||
@@ -63,14 +67,23 @@ void VulkanSwapChain::update() {
|
||||
mColors.reserve(bundle.colors.size());
|
||||
VkDevice const device = mPlatform->getDevice();
|
||||
|
||||
for (auto const color: bundle.colors) {
|
||||
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
|
||||
color, bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
|
||||
TextureUsage::COLOR_ATTACHMENT, mStagePool, true /* heap allocated */));
|
||||
TextureUsage depthUsage = TextureUsage::DEPTH_ATTACHMENT;
|
||||
TextureUsage colorUsage = TextureUsage::COLOR_ATTACHMENT;
|
||||
if (bundle.isProtected) {
|
||||
depthUsage |= TextureUsage::PROTECTED;
|
||||
colorUsage |= TextureUsage::PROTECTED;
|
||||
}
|
||||
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
|
||||
bundle.depth, bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
|
||||
TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */);
|
||||
for (auto const color: bundle.colors) {
|
||||
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
|
||||
device, mAllocator, mResourceManager, mCommands, color, bundle.colorFormat, 1,
|
||||
bundle.extent.width, bundle.extent.height, TextureUsage::COLOR_ATTACHMENT,
|
||||
mStagePool);
|
||||
mColors.push_back(colorTexture);
|
||||
}
|
||||
|
||||
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, device, mAllocator,
|
||||
mResourceManager, mCommands, bundle.depth, bundle.depthFormat, 1, bundle.extent.width,
|
||||
bundle.extent.height, TextureUsage::DEPTH_ATTACHMENT, mStagePool);
|
||||
|
||||
mExtent = bundle.extent;
|
||||
}
|
||||
@@ -89,7 +102,7 @@ void VulkanSwapChain::present() {
|
||||
}
|
||||
|
||||
mCommands->flush();
|
||||
|
||||
|
||||
// call the image ready wait function
|
||||
if (mExplicitImageReadyWait != nullptr) {
|
||||
mExplicitImageReadyWait(swapChain);
|
||||
@@ -129,7 +142,7 @@ void VulkanSwapChain::acquire(bool& resized) {
|
||||
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
|
||||
mCurrentSwapIndex = imageSyncData.imageIndex;
|
||||
mExplicitImageReadyWait = imageSyncData.explicitImageReadyWait;
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
||||
<< "Cannot acquire in swapchain.";
|
||||
if (imageSyncData.imageReadySemaphore != VK_NULL_HANDLE) {
|
||||
mCommands->injectDependency(imageSyncData.imageReadySemaphore);
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "vulkan/memory/Resource.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
@@ -36,14 +36,14 @@ namespace filament::backend {
|
||||
|
||||
struct VulkanHeadlessSwapChain;
|
||||
struct VulkanSurfaceSwapChain;
|
||||
class VulkanResourceAllocator;
|
||||
class VulkanCommands;
|
||||
|
||||
// A wrapper around the platform implementation of swapchain.
|
||||
struct VulkanSwapChain : public HwSwapChain, VulkanResource {
|
||||
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
|
||||
VulkanStagePool& stagePool,
|
||||
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
|
||||
struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
|
||||
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
|
||||
fvkmemory::ResourceManager* resourceManager, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow,
|
||||
uint64_t flags, VkExtent2D extent = {0, 0});
|
||||
|
||||
~VulkanSwapChain();
|
||||
|
||||
@@ -51,15 +51,15 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource {
|
||||
|
||||
void acquire(bool& reized);
|
||||
|
||||
inline VulkanTexture* getCurrentColor() const noexcept {
|
||||
fvkmemory::resource_ptr<VulkanTexture> getCurrentColor() const noexcept {
|
||||
uint32_t const imageIndex = mCurrentSwapIndex;
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
imageIndex != VulkanPlatform::ImageSyncData::INVALID_IMAGE_INDEX);
|
||||
return mColors[imageIndex].get();
|
||||
return mColors[imageIndex];
|
||||
}
|
||||
|
||||
inline VulkanTexture* getDepth() const noexcept {
|
||||
return mDepth.get();
|
||||
inline fvkmemory::resource_ptr<VulkanTexture> getDepth() const noexcept {
|
||||
return mDepth;
|
||||
}
|
||||
|
||||
inline bool isFirstRenderPass() const noexcept {
|
||||
@@ -74,15 +74,18 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource {
|
||||
return mExtent;
|
||||
}
|
||||
|
||||
inline bool isProtected() noexcept {
|
||||
return mPlatform->isProtected(swapChain);
|
||||
}
|
||||
private:
|
||||
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
void update();
|
||||
|
||||
VulkanPlatform* mPlatform;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
VulkanCommands* mCommands;
|
||||
VmaAllocator mAllocator;
|
||||
VulkanResourceAllocator* const mHandleAllocator;
|
||||
VulkanStagePool& mStagePool;
|
||||
bool const mHeadless;
|
||||
bool const mFlushAndWaitOnResize;
|
||||
@@ -90,8 +93,8 @@ private:
|
||||
|
||||
// We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout
|
||||
// transitions, which are useful here.
|
||||
utils::FixedCapacityVector<std::unique_ptr<VulkanTexture>> mColors;
|
||||
std::unique_ptr<VulkanTexture> mDepth;
|
||||
utils::FixedCapacityVector<fvkmemory::resource_ptr<VulkanTexture>> mColors;
|
||||
fvkmemory::resource_ptr<VulkanTexture> mDepth;
|
||||
VkExtent2D mExtent;
|
||||
uint32_t mCurrentSwapIndex;
|
||||
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <DataReshaper.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -147,72 +148,55 @@ inline VulkanLayout getDefaultLayoutImpl(VkImageUsageFlags vkusage) {
|
||||
|
||||
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
|
||||
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout)
|
||||
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
|
||||
mVkFormat(format),
|
||||
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout,
|
||||
bool isProtected)
|
||||
: mVkFormat(format),
|
||||
mViewType(viewType),
|
||||
mFullViewRange{filament::backend::getImageAspect(format), 0, levels, 0, layerCount},
|
||||
mDefaultLayout(defaultLayout),
|
||||
mIsProtected(isProtected),
|
||||
mStagePool(stagePool),
|
||||
mDevice(device),
|
||||
mAllocator(allocator),
|
||||
mCommands(commands),
|
||||
mIsTransientAttachment(false) {}
|
||||
|
||||
VulkanTextureState* VulkanTexture::getSharedState() {
|
||||
VulkanTextureState* state = mAllocator->handle_cast<VulkanTextureState*>(mState);
|
||||
return state;
|
||||
}
|
||||
|
||||
VulkanTextureState const* VulkanTexture::getSharedState() const {
|
||||
VulkanTextureState const* state = mAllocator->handle_cast<VulkanTextureState const*>(mState);
|
||||
return state;
|
||||
}
|
||||
|
||||
// Constructor for internally passed VkImage
|
||||
VulkanTexture::VulkanTexture(
|
||||
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height,
|
||||
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
|
||||
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
|
||||
tusage),
|
||||
VulkanResource(
|
||||
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
|
||||
mAllocator(handleAllocator),
|
||||
mState(handleAllocator->initHandle<VulkanTextureState>(
|
||||
device, allocator, commands, stagePool,
|
||||
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1,
|
||||
getDefaultLayoutImpl(tusage))) {
|
||||
auto* const state = getSharedState();
|
||||
state->mTextureImage = image;
|
||||
mPrimaryViewRange = state->mFullViewRange;
|
||||
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
|
||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
||||
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
|
||||
VulkanStagePool& stagePool)
|
||||
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
|
||||
tusage),
|
||||
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
|
||||
allocator, commands, stagePool, format, imgutil::getViewType(SamplerType::SAMPLER_2D),
|
||||
1, 1, getDefaultLayoutImpl(tusage), any(usage & TextureUsage::PROTECTED))) {
|
||||
mState->mTextureImage = image;
|
||||
mPrimaryViewRange = mState->mFullViewRange;
|
||||
}
|
||||
|
||||
// Constructor for user facing texture
|
||||
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels,
|
||||
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
|
||||
VulkanContext const& context, VmaAllocator allocator,
|
||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
|
||||
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
|
||||
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
|
||||
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
|
||||
VulkanResource(
|
||||
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
|
||||
mAllocator(handleAllocator),
|
||||
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
|
||||
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
|
||||
getLayerCount(target, depth), VulkanLayout::UNDEFINED)) {
|
||||
auto* const state = getSharedState();
|
||||
|
||||
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
|
||||
allocator, commands, stagePool, backend::getVkFormat(tformat),
|
||||
imgutil::getViewType(target), levels, getLayerCount(target, depth),
|
||||
VulkanLayout::UNDEFINED, any(usage & TextureUsage::PROTECTED))) {
|
||||
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
||||
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
|
||||
.format = state->mVkFormat,
|
||||
.extent = {w, h, depth},
|
||||
.mipLevels = levels,
|
||||
.arrayLayers = 1,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = 0};
|
||||
VkImageCreateInfo imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
|
||||
.format = mState->mVkFormat,
|
||||
.extent = {w, h, depth},
|
||||
.mipLevels = levels,
|
||||
.arrayLayers = 1,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = 0,
|
||||
};
|
||||
if (target == SamplerType::SAMPLER_CUBEMAP) {
|
||||
imageInfo.arrayLayers = 6;
|
||||
imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
||||
@@ -232,6 +216,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.arrayLayers = depth * 6;
|
||||
imageInfo.extent.depth = 1;
|
||||
}
|
||||
if (any(usage & TextureUsage::PROTECTED)) {
|
||||
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
@@ -248,7 +235,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(tusage & TextureUsage::ALL_ATTACHMENTS);
|
||||
state->mIsTransientAttachment = useTransientAttachment;
|
||||
mState->mIsTransientAttachment = useTransientAttachment;
|
||||
|
||||
const VkImageUsageFlags transientFlag =
|
||||
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
|
||||
@@ -293,7 +280,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
// any kind of attachment (color or depth).
|
||||
const auto& limits = context.getPhysicalDeviceLimits();
|
||||
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
|
||||
samples = reduceSampleCount(samples, isVkDepthFormat(state->mVkFormat)
|
||||
samples = reduceSampleCount(samples, isVkDepthFormat(mState->mVkFormat)
|
||||
? limits.sampledImageDepthSampleCounts
|
||||
: limits.sampledImageColorSampleCounts);
|
||||
}
|
||||
@@ -307,12 +294,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
this->samples = samples;
|
||||
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
||||
|
||||
VkResult error = vkCreateImage(state->mDevice, &imageInfo, VKALLOC, &state->mTextureImage);
|
||||
VkResult error = vkCreateImage(mState->mDevice, &imageInfo, VKALLOC, &mState->mTextureImage);
|
||||
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
||||
FVK_LOGD << "vkCreateImage: "
|
||||
<< "image = " << state->mTextureImage << ", "
|
||||
<< "image = " << mState->mTextureImage << ", "
|
||||
<< "result = " << error << ", "
|
||||
<< "handle = " << utils::io::hex << state->mTextureImage << utils::io::dec << ", "
|
||||
<< "handle = " << utils::io::hex << mState->mTextureImage << utils::io::dec << ", "
|
||||
<< "extent = " << w << "x" << h << "x"<< depth << ", "
|
||||
<< "mipLevels = " << int(levels) << ", "
|
||||
<< "TextureUsage = " << static_cast<int>(usage) << ", "
|
||||
@@ -321,17 +308,18 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
<< "type = " << imageInfo.imageType << ", "
|
||||
<< "flags = " << imageInfo.flags << ", "
|
||||
<< "target = " << static_cast<int>(target) <<", "
|
||||
<< "format = " << state->mVkFormat << utils::io::endl;
|
||||
<< "format = " << mState->mVkFormat << utils::io::endl;
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image.";
|
||||
|
||||
// Allocate memory for the VkImage and bind it.
|
||||
VkMemoryRequirements memReqs = {};
|
||||
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
|
||||
vkGetImageMemoryRequirements(mState->mDevice, mState->mTextureImage, &memReqs);
|
||||
|
||||
const VkFlags requiredMemoryFlags =
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U);
|
||||
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
|
||||
(mState->mIsProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
|
||||
uint32_t memoryTypeIndex
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
@@ -343,37 +331,29 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
error = vkAllocateMemory(state->mDevice, &allocInfo, nullptr, &state->mTextureImageMemory);
|
||||
error = vkAllocateMemory(mState->mDevice, &allocInfo, nullptr, &mState->mTextureImageMemory);
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory.";
|
||||
error = vkBindImageMemory(state->mDevice, state->mTextureImage, state->mTextureImageMemory, 0);
|
||||
error = vkBindImageMemory(mState->mDevice, mState->mTextureImage, mState->mTextureImageMemory,
|
||||
0);
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to bind image.";
|
||||
|
||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||
mPrimaryViewRange = state->mFullViewRange;
|
||||
mPrimaryViewRange = mState->mFullViewRange;
|
||||
|
||||
// Go ahead and create the primary image view.
|
||||
getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
|
||||
getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
|
||||
|
||||
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
|
||||
commandsBuf.acquire(this);
|
||||
|
||||
auto const defaultLayout = state->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
|
||||
transitionLayout(&commandsBuf, mPrimaryViewRange, defaultLayout);
|
||||
mState->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
|
||||
}
|
||||
|
||||
// Constructor for creating a texture view
|
||||
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator, VulkanTexture const* src, uint8_t baseLevel,
|
||||
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
|
||||
uint8_t levelCount)
|
||||
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage),
|
||||
VulkanResource(VulkanResourceType::TEXTURE),
|
||||
mAllocator(handleAllocator) {
|
||||
src->format, src->usage) {
|
||||
mState = src->mState;
|
||||
auto* state = getSharedState();
|
||||
|
||||
state->refs++;
|
||||
mPrimaryViewRange = src->mPrimaryViewRange;
|
||||
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
|
||||
mPrimaryViewRange.levelCount = levelCount;
|
||||
@@ -382,31 +362,21 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
// Constructor for creating a texture view with swizzle
|
||||
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator, VulkanTexture const* src,
|
||||
VkComponentMapping swizzle)
|
||||
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
|
||||
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage),
|
||||
VulkanResource(VulkanResourceType::TEXTURE),
|
||||
mAllocator(handleAllocator) {
|
||||
src->format, src->usage) {
|
||||
mState = src->mState;
|
||||
auto* state = getSharedState();
|
||||
state->refs++;
|
||||
mPrimaryViewRange = src->mPrimaryViewRange;
|
||||
mSwizzle = composeSwizzle(src->mSwizzle, swizzle);
|
||||
}
|
||||
|
||||
VulkanTexture::~VulkanTexture() {
|
||||
auto* const state = getSharedState();
|
||||
state->refs--;
|
||||
if (state->refs == 0) {
|
||||
if (state->mTextureImageMemory != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(state->mDevice, state->mTextureImage, VKALLOC);
|
||||
vkFreeMemory(state->mDevice, state->mTextureImageMemory, VKALLOC);
|
||||
}
|
||||
for (auto entry: state->mCachedImageViews) {
|
||||
vkDestroyImageView(state->mDevice, entry.second, VKALLOC);
|
||||
}
|
||||
mAllocator->destruct<VulkanTextureState>(mState);
|
||||
VulkanTextureState::~VulkanTextureState() {
|
||||
if (mTextureImageMemory != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
|
||||
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
|
||||
}
|
||||
for (auto entry: mCachedImageViews) {
|
||||
vkDestroyImageView(mDevice, entry.second, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +385,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
assert_invariant(width <= this->width && height <= this->height);
|
||||
assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP ||
|
||||
target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1));
|
||||
auto* const state = getSharedState();
|
||||
assert_invariant(!mState->mIsProtected);
|
||||
const PixelBufferDescriptor* hostData = &data;
|
||||
PixelBufferDescriptor reshapedData;
|
||||
|
||||
@@ -428,7 +398,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
|
||||
// If format conversion is both required and supported, use vkCmdBlitImage.
|
||||
const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type);
|
||||
const VkFormat deviceFormat = getVkFormatLinear(state->mVkFormat);
|
||||
const VkFormat deviceFormat = getVkFormatLinear(mState->mVkFormat);
|
||||
if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) {
|
||||
assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 &&
|
||||
"Offsets not yet supported when format conversion is required.");
|
||||
@@ -440,16 +410,16 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
void* mapped = nullptr;
|
||||
VulkanStage const* stage = state->mStagePool.acquireStage(hostData->size);
|
||||
VulkanStage const* stage = mState->mStagePool.acquireStage(hostData->size);
|
||||
assert_invariant(stage->memory);
|
||||
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
|
||||
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
|
||||
memcpy(mapped, hostData->buffer, hostData->size);
|
||||
vmaUnmapMemory(state->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size);
|
||||
vmaUnmapMemory(mState->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, hostData->size);
|
||||
|
||||
VulkanCommandBuffer& commands = state->mCommands->get();
|
||||
VulkanCommandBuffer& commands = mState->mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(this);
|
||||
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
|
||||
|
||||
VkBufferImageCopy copyRegion = {
|
||||
.bufferOffset = {},
|
||||
@@ -495,25 +465,24 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
|
||||
transitionLayout(&commands, transitionRange, newLayout);
|
||||
|
||||
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, ©Region);
|
||||
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mState->mTextureImage, newVkLayout, 1, ©Region);
|
||||
|
||||
transitionLayout(&commands, transitionRange, nextLayout);
|
||||
}
|
||||
|
||||
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel) {
|
||||
auto* const state = getSharedState();
|
||||
void* mapped = nullptr;
|
||||
VulkanStageImage const* stage
|
||||
= state->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
|
||||
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
|
||||
= mState->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
|
||||
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
|
||||
memcpy(mapped, hostData.buffer, hostData.size);
|
||||
vmaUnmapMemory(state->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData.size);
|
||||
vmaUnmapMemory(mState->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, hostData.size);
|
||||
|
||||
VulkanCommandBuffer& commands = state->mCommands->get();
|
||||
VulkanCommandBuffer& commands = mState->mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(this);
|
||||
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
|
||||
|
||||
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
||||
const int layer = 0;
|
||||
@@ -536,14 +505,13 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
||||
transitionLayout(&commands, range, newLayout);
|
||||
|
||||
vkCmdBlitImage(cmdbuf, stage->image, imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
state->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
|
||||
mState->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
|
||||
|
||||
transitionLayout(&commands, range, oldLayout);
|
||||
}
|
||||
|
||||
VulkanLayout VulkanTexture::getDefaultLayout() const {
|
||||
auto* const state = getSharedState();
|
||||
return state->mDefaultLayout;
|
||||
return mState->mDefaultLayout;
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
||||
@@ -562,42 +530,43 @@ VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range,
|
||||
|
||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle) {
|
||||
auto* const state = getSharedState();
|
||||
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
|
||||
auto iter = state->mCachedImageViews.find(key);
|
||||
if (iter != state->mCachedImageViews.end()) {
|
||||
auto iter = mState->mCachedImageViews.find(key);
|
||||
if (iter != mState->mCachedImageViews.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = state->mTextureImage,
|
||||
.image = mState->mTextureImage,
|
||||
.viewType = viewType,
|
||||
.format = state->mVkFormat,
|
||||
.format = mState->mVkFormat,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
VkImageView imageView;
|
||||
vkCreateImageView(state->mDevice, &viewInfo, VKALLOC, &imageView);
|
||||
state->mCachedImageViews.emplace(key, imageView);
|
||||
vkCreateImageView(mState->mDevice, &viewInfo, VKALLOC, &imageView);
|
||||
mState->mCachedImageViews.emplace(key, imageView);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
VkImageAspectFlags VulkanTexture::getImageAspect() const {
|
||||
// Helper function in VulkanUtility
|
||||
auto* const state = getSharedState();
|
||||
return filament::backend::getImageAspect(state->mVkFormat);
|
||||
return filament::backend::getImageAspect(mState->mVkFormat);
|
||||
}
|
||||
|
||||
bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
|
||||
VkImageSubresourceRange const& range, VulkanLayout newLayout) {
|
||||
return transitionLayout(commands->buffer(), range, newLayout);
|
||||
if (transitionLayout(commands->buffer(), range, newLayout)) {
|
||||
commands->acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range,
|
||||
VulkanLayout newLayout) {
|
||||
auto* const state = getSharedState();
|
||||
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
|
||||
|
||||
uint32_t const firstLayer = range.baseArrayLayer;
|
||||
@@ -628,7 +597,7 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
|
||||
continue;
|
||||
}
|
||||
hasTransitions = hasTransitions || imgutil::transitionLayout(cmdbuf, {
|
||||
.image = state->mTextureImage,
|
||||
.image = mState->mTextureImage,
|
||||
.oldLayout = layout,
|
||||
.newLayout = newLayout,
|
||||
.subresources = {
|
||||
@@ -643,7 +612,7 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
|
||||
}
|
||||
} else if (newLayout != oldLayout) {
|
||||
hasTransitions = imgutil::transitionLayout(cmdbuf, {
|
||||
.image = state->mTextureImage,
|
||||
.image = mState->mTextureImage,
|
||||
.oldLayout = oldLayout,
|
||||
.newLayout = newLayout,
|
||||
.subresources = range,
|
||||
@@ -676,7 +645,6 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
|
||||
void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
|
||||
VkImageSubresourceRange const& range) {
|
||||
VkCommandBuffer const cmdbuf = commands->buffer();
|
||||
auto* const state = getSharedState();
|
||||
VkImageLayout const layout =
|
||||
imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
|
||||
VkImageMemoryBarrier barrier = {
|
||||
@@ -688,7 +656,7 @@ void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
|
||||
.newLayout = layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = state->mTextureImage,
|
||||
.image = mState->mTextureImage,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
@@ -700,7 +668,6 @@ void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
|
||||
void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
|
||||
VkImageSubresourceRange const& range) {
|
||||
VkCommandBuffer const cmdbuf = commands->buffer();
|
||||
auto* const state = getSharedState();
|
||||
VkImageLayout const layout
|
||||
= imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
|
||||
VkImageMemoryBarrier barrier = {
|
||||
@@ -711,7 +678,7 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
|
||||
.newLayout = layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = state->mTextureImage,
|
||||
.image = mState->mTextureImage,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
@@ -719,7 +686,6 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
|
||||
}
|
||||
|
||||
void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout) {
|
||||
auto* const state = getSharedState();
|
||||
uint32_t const firstLayer = range.baseArrayLayer;
|
||||
uint32_t const lastLayer = firstLayer + range.layerCount;
|
||||
uint32_t const firstLevel = range.baseMipLevel;
|
||||
@@ -732,34 +698,32 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
|
||||
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
|
||||
uint32_t const first = (layer << 16) | firstLevel;
|
||||
uint32_t const last = (layer << 16) | lastLevel;
|
||||
state->mSubresourceLayouts.clear(first, last);
|
||||
mState->mSubresourceLayouts.clear(first, last);
|
||||
}
|
||||
} else {
|
||||
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
|
||||
uint32_t const first = (layer << 16) | firstLevel;
|
||||
uint32_t const last = (layer << 16) | lastLevel;
|
||||
state->mSubresourceLayouts.add(first, last, newLayout);
|
||||
mState->mSubresourceLayouts.add(first, last, newLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
|
||||
assert_invariant(level <= 0xffff && layer <= 0xffff);
|
||||
auto* const state = getSharedState();
|
||||
const uint32_t key = (layer << 16) | level;
|
||||
if (!state->mSubresourceLayouts.has(key)) {
|
||||
if (!mState->mSubresourceLayouts.has(key)) {
|
||||
return VulkanLayout::UNDEFINED;
|
||||
}
|
||||
return state->mSubresourceLayouts.get(key);
|
||||
return mState->mSubresourceLayouts.get(key);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
void VulkanTexture::print() const {
|
||||
auto* const state = getSharedState();
|
||||
uint32_t const firstLayer = 0;
|
||||
uint32_t const lastLayer = firstLayer + state->mFullViewRange.layerCount;
|
||||
uint32_t const lastLayer = firstLayer + mState->mFullViewRange.layerCount;
|
||||
uint32_t const firstLevel = 0;
|
||||
uint32_t const lastLevel = firstLevel + state->mFullViewRange.levelCount;
|
||||
uint32_t const lastLevel = firstLevel + mState->mFullViewRange.levelCount;
|
||||
|
||||
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
|
||||
for (uint32_t level = firstLevel; level < lastLevel; ++level) {
|
||||
@@ -768,16 +732,16 @@ void VulkanTexture::print() const {
|
||||
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
|
||||
level >= mPrimaryViewRange.baseMipLevel &&
|
||||
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
|
||||
FVK_LOGD << "[" << state->mTextureImage << "]: (" << layer << "," << level
|
||||
FVK_LOGD << "[" << mState->mTextureImage << "]: (" << layer << "," << level
|
||||
<< ")=" << getLayout(layer, level)
|
||||
<< " primary=" << primary
|
||||
<< utils::io::endl;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto view: state->mCachedImageViews) {
|
||||
for (auto view: mState->mCachedImageViews) {
|
||||
auto& range = view.first.range;
|
||||
FVK_LOGD << "[" << state->mTextureImage << ", imageView=" << view.second << "]=>"
|
||||
FVK_LOGD << "[" << mState->mTextureImage << ", imageView=" << view.second << "]=>"
|
||||
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
|
||||
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
|
||||
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "vulkan/memory/Resource.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/RangeMap.h>
|
||||
@@ -30,12 +31,12 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanResourceAllocator;
|
||||
|
||||
struct VulkanTextureState : public VulkanResource {
|
||||
struct VulkanTextureState : public fvkmemory::Resource {
|
||||
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
|
||||
uint8_t layerCount, VulkanLayout defaultLayout);
|
||||
uint8_t layerCount, VulkanLayout defaultLayout, bool isProtected);
|
||||
|
||||
~VulkanTextureState();
|
||||
|
||||
struct ImageViewKey {
|
||||
VkImageSubresourceRange range; // 4 * 5 bytes
|
||||
@@ -58,10 +59,8 @@ struct VulkanTextureState : public VulkanResource {
|
||||
|
||||
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
||||
|
||||
uint32_t refs = 1;
|
||||
|
||||
// The texture with the sidecar owns the sidecar.
|
||||
std::unique_ptr<VulkanTexture> mSidecarMSAA;
|
||||
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
|
||||
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
|
||||
|
||||
VkFormat const mVkFormat;
|
||||
@@ -70,6 +69,8 @@ struct VulkanTextureState : public VulkanResource {
|
||||
VkImage mTextureImage = VK_NULL_HANDLE;
|
||||
VulkanLayout mDefaultLayout;
|
||||
|
||||
bool mIsProtected = false;
|
||||
|
||||
// Track the image layout of each subresource using a sparse range map.
|
||||
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
|
||||
|
||||
@@ -81,37 +82,32 @@ struct VulkanTextureState : public VulkanResource {
|
||||
bool mIsTransientAttachment;
|
||||
};
|
||||
|
||||
|
||||
struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
// Standard constructor for user-facing textures.
|
||||
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
|
||||
VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
SamplerType target, uint8_t levels,
|
||||
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false);
|
||||
VmaAllocator allocator, fvkmemory::ResourceManager* resourceManager,
|
||||
VulkanCommands* commands, SamplerType target, uint8_t levels, TextureFormat tformat,
|
||||
uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage tusage,
|
||||
VulkanStagePool& stagePool);
|
||||
|
||||
// Specialized constructor for internally created textures (e.g. from a swap chain)
|
||||
// The texture will never destroy the given VkImage, but it does manages its subresources.
|
||||
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
VkImage image,
|
||||
VulkanTexture(VkDevice device, VmaAllocator allocator,
|
||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
||||
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
|
||||
VulkanStagePool& stagePool, bool heapAllocated = false);
|
||||
VulkanStagePool& stagePool);
|
||||
|
||||
// Constructor for creating a texture view for wrt specific mip range
|
||||
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
|
||||
VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount);
|
||||
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel, uint8_t levelCount);
|
||||
|
||||
// Constructor for creating a texture view for swizzle.
|
||||
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
|
||||
VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
VulkanTexture const* src, VkComponentMapping swizzle);
|
||||
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle);
|
||||
|
||||
~VulkanTexture();
|
||||
~VulkanTexture() = default;
|
||||
|
||||
// Uploads data into a subregion of a 2D or 3D texture.
|
||||
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
@@ -119,16 +115,14 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
|
||||
// Returns the primary image view, which is used for shader sampling.
|
||||
VkImageView getPrimaryImageView() {
|
||||
VulkanTextureState* state = getSharedState();
|
||||
return getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
|
||||
return getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageViewType getViewType() const {
|
||||
VulkanTextureState const* state = getSharedState();
|
||||
return state->mViewType;
|
||||
return mState->mViewType;
|
||||
}
|
||||
|
||||
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
|
||||
VkImageSubresourceRange const& getPrimaryViewRange() const { return mPrimaryViewRange; }
|
||||
|
||||
VulkanLayout getPrimaryImageLayout() const {
|
||||
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
|
||||
@@ -154,29 +148,28 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
|
||||
|
||||
VkFormat getVkFormat() const {
|
||||
VulkanTextureState const* state = getSharedState();
|
||||
return state->mVkFormat;
|
||||
return mState->mVkFormat;
|
||||
}
|
||||
VkImage getVkImage() const {
|
||||
VulkanTextureState const* state = getSharedState();
|
||||
return state->mTextureImage;
|
||||
return mState->mTextureImage;
|
||||
}
|
||||
|
||||
VulkanLayout getLayout(uint32_t layer, uint32_t level) const;
|
||||
|
||||
void setSidecar(VulkanTexture* sidecar) {
|
||||
VulkanTextureState* state = getSharedState();
|
||||
state->mSidecarMSAA.reset(sidecar);
|
||||
void setSidecar(fvkmemory::resource_ptr<VulkanTexture> sidecar) {
|
||||
mState->mSidecarMSAA = sidecar;
|
||||
}
|
||||
|
||||
VulkanTexture* getSidecar() const {
|
||||
VulkanTextureState const* state = getSharedState();
|
||||
return state->mSidecarMSAA.get();
|
||||
fvkmemory::resource_ptr<VulkanTexture> getSidecar() const {
|
||||
return mState->mSidecarMSAA;
|
||||
}
|
||||
|
||||
bool isTransientAttachment() const {
|
||||
VulkanTextureState const* state = getSharedState();
|
||||
return state->mIsTransientAttachment;
|
||||
return mState->mIsTransientAttachment;
|
||||
}
|
||||
|
||||
bool getIsProtected() const {
|
||||
return mState->mIsProtected;
|
||||
}
|
||||
|
||||
bool transitionLayout(VulkanCommandBuffer* commands, VkImageSubresourceRange const& range,
|
||||
@@ -204,9 +197,6 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
#endif
|
||||
|
||||
private:
|
||||
VulkanTextureState* getSharedState();
|
||||
VulkanTextureState const* getSharedState() const;
|
||||
|
||||
// Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and
|
||||
// swizzle (or not).
|
||||
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
@@ -215,9 +205,7 @@ private:
|
||||
void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint32_t miplevel);
|
||||
|
||||
VulkanResourceAllocator* const mAllocator;
|
||||
|
||||
Handle<VulkanTextureState> mState;
|
||||
fvkmemory::resource_ptr<VulkanTextureState> mState;
|
||||
|
||||
// Track the range of subresources that define the "primary" image view, which is the special
|
||||
// image view that gets bound to an actual texture sampler.
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
#include "VulkanDescriptorSetManager.h"
|
||||
|
||||
#include <vulkan/VulkanCommands.h>
|
||||
#include <vulkan/VulkanHandles.h>
|
||||
#include <vulkan/VulkanUtility.h>
|
||||
#include <vulkan/VulkanConstants.h>
|
||||
#include <vulkan/VulkanImageUtility.h>
|
||||
#include <vulkan/VulkanResources.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -274,7 +274,7 @@ public:
|
||||
DescriptorInfinitePool(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
|
||||
VkDescriptorSet obtainSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
DescriptorPool* sameTypePool = nullptr;
|
||||
for (auto& pool: mPools) {
|
||||
@@ -325,13 +325,12 @@ public:
|
||||
DescriptorSetLayoutManager(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout* layout) {
|
||||
auto const& bitmasks = layout->bitmask;
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks) {
|
||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
auto vklayout = createLayout(mDevice, layout->bitmask);
|
||||
mVkLayouts[layout->bitmask] = vklayout;
|
||||
auto vklayout = createLayout(mDevice, bitmasks);
|
||||
mVkLayouts[bitmasks] = vklayout;
|
||||
return vklayout;
|
||||
}
|
||||
|
||||
@@ -347,74 +346,11 @@ private:
|
||||
mVkLayouts;
|
||||
};
|
||||
|
||||
class VulkanDescriptorSetManager::DescriptorSetHistory {
|
||||
private:
|
||||
using TextureBundle = std::pair<VulkanTexture*, VkImageSubresourceRange>;
|
||||
|
||||
public:
|
||||
DescriptorSetHistory()
|
||||
: dynamicUboCount(0),
|
||||
mResources(nullptr) {}
|
||||
|
||||
DescriptorSetHistory(UniformBufferBitmask const& dynamicUbo, uint8_t uniqueDynamicUboCount,
|
||||
VulkanResourceAllocator* allocator, VulkanDescriptorSet* set)
|
||||
: dynamicUboMask(dynamicUbo),
|
||||
dynamicUboCount(uniqueDynamicUboCount),
|
||||
mResources(allocator),
|
||||
mSet(set),
|
||||
mBound(false) {
|
||||
assert_invariant(set);
|
||||
// initial state is unbound.
|
||||
unbind();
|
||||
}
|
||||
|
||||
~DescriptorSetHistory() {
|
||||
if (mSet) {
|
||||
mResources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||
mOffsets = std::move(offsets);
|
||||
mBound = false;
|
||||
}
|
||||
|
||||
void write(uint8_t binding) noexcept { mBound = false; }
|
||||
|
||||
// Ownership will be transfered to the commandbuffer.
|
||||
void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
|
||||
uint8_t index) noexcept {
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
||||
1, &mSet->vkSet, dynamicUboCount, mOffsets.data());
|
||||
|
||||
commands->acquire(mSet);
|
||||
mResources.clear();
|
||||
mBound = true;
|
||||
}
|
||||
|
||||
void unbind() noexcept {
|
||||
mResources.acquire(mSet);
|
||||
mBound = false;
|
||||
}
|
||||
|
||||
bool bound() const noexcept { return mBound; }
|
||||
|
||||
UniformBufferBitmask const dynamicUboMask;
|
||||
uint8_t const dynamicUboCount;
|
||||
|
||||
private:
|
||||
FixedSizeVulkanResourceManager<1> mResources;
|
||||
VulkanDescriptorSet* mSet = nullptr;
|
||||
|
||||
backend::DescriptorSetOffsetArray mOffsets;
|
||||
bool mBound = false;
|
||||
};
|
||||
|
||||
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
|
||||
VulkanResourceAllocator* resourceAllocator)
|
||||
fvkmemory::ResourceManager* resourceManager)
|
||||
: mDevice(device),
|
||||
mResourceAllocator(resourceAllocator),
|
||||
mResourceManager(resourceManager),
|
||||
mLayoutManager(std::make_unique<DescriptorSetLayoutManager>(device)),
|
||||
mDescriptorPool(std::make_unique<DescriptorInfinitePool>(device)) {}
|
||||
|
||||
@@ -423,64 +359,72 @@ VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default;
|
||||
void VulkanDescriptorSetManager::terminate() noexcept{
|
||||
mLayoutManager.reset();
|
||||
mDescriptorPool.reset();
|
||||
mHistory.clear();
|
||||
clearHistory();
|
||||
}
|
||||
|
||||
// bind() is not really binding the set but just stashing until we have all the info
|
||||
// (pipelinelayout).
|
||||
void VulkanDescriptorSetManager::bind(uint8_t setIndex, VulkanDescriptorSet* set,
|
||||
void VulkanDescriptorSetManager::bind(uint8_t setIndex,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
auto history = mHistory[set].get();
|
||||
history->setOffsets(std::move(offsets));
|
||||
set->setOffsets(std::move(offsets));
|
||||
mStashedSets[setIndex] = set;
|
||||
}
|
||||
|
||||
auto lastHistory = mStashedSets[setIndex];
|
||||
if (lastHistory) {
|
||||
lastHistory->unbind();
|
||||
}
|
||||
mStashedSets[setIndex] = history;
|
||||
void VulkanDescriptorSetManager::unbind(uint8_t setIndex) {
|
||||
mStashedSets[setIndex] = {};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
|
||||
VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) {
|
||||
DescriptorSetHistoryArray& updateSets = mStashedSets;
|
||||
|
||||
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
|
||||
// actual set of sets that *needs* to be bound.
|
||||
DescriptorSetMask curMask = setMask;
|
||||
|
||||
auto& updateSets = mStashedSets;
|
||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index] || updateSets[index]->bound()) {
|
||||
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
|
||||
BoundInfo nextInfo = {
|
||||
pipelineLayout,
|
||||
setMask,
|
||||
updateSets,
|
||||
};
|
||||
if (curMask.none() && mLastBoundInfo == nextInfo) {
|
||||
if (curMask.none() &&
|
||||
(mLastBoundInfo.pipelineLayout == pipelineLayout && mLastBoundInfo.setMask == setMask &&
|
||||
mLastBoundInfo.boundSets == updateSets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
|
||||
updateSets[index]->bind(commands, pipelineLayout, index);
|
||||
// This code actually binds the descriptor sets.
|
||||
auto set = updateSets[index];
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
||||
1, &set->vkSet, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
commands->acquire(set);
|
||||
});
|
||||
mLastBoundInfo = nextInfo;
|
||||
|
||||
mStashedSets = {};
|
||||
|
||||
mLastBoundInfo = {
|
||||
pipelineLayout,
|
||||
setMask,
|
||||
updateSets,
|
||||
};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
void VulkanDescriptorSetManager::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
|
||||
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
VkDescriptorBufferInfo const info = {
|
||||
.buffer = bufferObject->buffer.getGpuBuffer(),
|
||||
.offset = offset,
|
||||
.range = size,
|
||||
};
|
||||
|
||||
VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
auto& history = mHistory[set];
|
||||
|
||||
if (history->dynamicUboMask.test(binding)) {
|
||||
if (set->dynamicUboMask.test(binding)) {
|
||||
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
}
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
@@ -494,11 +438,11 @@ void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
set->acquire(bufferObject);
|
||||
history->write(binding);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanTexture* texture, VkSampler sampler) noexcept {
|
||||
void VulkanDescriptorSetManager::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler) noexcept {
|
||||
VkDescriptorImageInfo info{
|
||||
.sampler = sampler,
|
||||
};
|
||||
@@ -526,47 +470,37 @@ void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
set->acquire(texture);
|
||||
mHistory[set]->write(binding);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set,
|
||||
VulkanAttachment attachment) noexcept {
|
||||
void VulkanDescriptorSetManager::updateInputAttachment(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
VulkanAttachment const& attachment) noexcept {
|
||||
// TOOD: fill-in this region
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::createSet(Handle<HwDescriptorSet> handle,
|
||||
VulkanDescriptorSetLayout* layout) {
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetManager::createSet(
|
||||
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vkSet = mDescriptorPool->obtainSet(layout);
|
||||
auto const& count = layout->count;
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
VulkanDescriptorSet* set = mResourceAllocator->construct<VulkanDescriptorSet>(handle,
|
||||
mResourceAllocator, vkSet, [vkSet, count, vklayout, this](VulkanDescriptorSet* set) {
|
||||
eraseSetFromHistory(set);
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
return fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle, vkSet,
|
||||
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
|
||||
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
||||
// down).
|
||||
if (mDescriptorPool) {
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
});
|
||||
mHistory[set] = std::make_unique<DescriptorSetHistory>(layout->bitmask.dynamicUbo,
|
||||
layout->count.dynamicUbo, mResourceAllocator, set);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::destroySet(Handle<HwDescriptorSet> handle) {
|
||||
VulkanDescriptorSet* set = mResourceAllocator->handle_cast<VulkanDescriptorSet*>(handle);
|
||||
eraseSetFromHistory(set);
|
||||
void VulkanDescriptorSetManager::initVkLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
layout->setVkLayout(mLayoutManager->getVkLayout(layout->bitmask));
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::initVkLayout(VulkanDescriptorSetLayout* layout) {
|
||||
layout->setVkLayout(mLayoutManager->getVkLayout(layout));
|
||||
void VulkanDescriptorSetManager::clearHistory() {
|
||||
mStashedSets = {};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::eraseSetFromHistory(VulkanDescriptorSet* set) {
|
||||
DescriptorSetHistory* history = mHistory[set].get();
|
||||
mHistory.erase(set);
|
||||
|
||||
for (uint8_t i = 0; i < mStashedSets.size(); ++i) {
|
||||
if (mStashedSets[i] == history) {
|
||||
mStashedSets[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
|
||||
#include <vulkan/VulkanResourceAllocator.h>
|
||||
#include <vulkan/VulkanTexture.h>
|
||||
#include <vulkan/VulkanUtility.h>
|
||||
#include "vulkan/VulkanHandles.h"
|
||||
#include "vulkan/VulkanTexture.h"
|
||||
#include "vulkan/VulkanUtility.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
@@ -46,71 +47,55 @@ public:
|
||||
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
|
||||
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
|
||||
VulkanDescriptorSetManager(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetManager();
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
void updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept;
|
||||
void updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanBufferObject> bufferObject, VkDeviceSize offset,
|
||||
VkDeviceSize size) noexcept;
|
||||
|
||||
void updateSampler(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanTexture* texture, VkSampler sampler) noexcept;
|
||||
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||
|
||||
void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept;
|
||||
void updateInputAttachment(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
VulkanAttachment const& attachment) noexcept;
|
||||
|
||||
void bind(uint8_t setIndex, VulkanDescriptorSet* set, backend::DescriptorSetOffsetArray&& offsets);
|
||||
void bind(uint8_t setIndex, fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
backend::DescriptorSetOffsetArray&& offsets);
|
||||
|
||||
void unbind(uint8_t setIndex);
|
||||
|
||||
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
|
||||
DescriptorSetMask const& setMask);
|
||||
|
||||
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
|
||||
VulkanBufferObject* bufferObject) noexcept;
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void createSet(Handle<HwDescriptorSet> handle, VulkanDescriptorSetLayout* layout);
|
||||
void initVkLayout(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void destroySet(Handle<HwDescriptorSet> handle);
|
||||
|
||||
void initVkLayout(VulkanDescriptorSetLayout* layout);
|
||||
void clearHistory();
|
||||
|
||||
private:
|
||||
class DescriptorSetHistory;
|
||||
class DescriptorSetLayoutManager;
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
void eraseSetFromHistory(VulkanDescriptorSet* set);
|
||||
|
||||
using DescriptorSetHistoryArray =
|
||||
std::array<DescriptorSetHistory*, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
struct BoundInfo {
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
DescriptorSetMask setMask;
|
||||
DescriptorSetHistoryArray boundSets;
|
||||
|
||||
bool operator==(BoundInfo const& info) const {
|
||||
if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) {
|
||||
return false;
|
||||
}
|
||||
bool equal = true;
|
||||
setMask.forEachSetBit([&](size_t i) {
|
||||
if (boundSets[i] != info.boundSets[i]) {
|
||||
equal = false;
|
||||
}
|
||||
});
|
||||
return equal;
|
||||
}
|
||||
};
|
||||
using DescriptorSetArray =
|
||||
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
VkDevice mDevice;
|
||||
VulkanResourceAllocator* mResourceAllocator;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
std::unique_ptr<DescriptorSetLayoutManager> mLayoutManager;
|
||||
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
|
||||
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
|
||||
std::unordered_map<VulkanDescriptorSet*, std::unique_ptr<DescriptorSetHistory>> mHistory;
|
||||
DescriptorSetHistoryArray mStashedSets = {};
|
||||
DescriptorSetArray mStashedSets = {};
|
||||
|
||||
BoundInfo mLastBoundInfo;
|
||||
struct {
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
DescriptorSetMask setMask;
|
||||
DescriptorSetArray boundSets = {};
|
||||
} mLastBoundInfo;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -16,12 +16,11 @@
|
||||
|
||||
#include "VulkanPipelineLayoutCache.h"
|
||||
|
||||
#include <vulkan/VulkanResourceAllocator.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
DescriptorSetLayoutArray const& descriptorSetLayouts, VulkanProgram* program) {
|
||||
DescriptorSetLayoutArray const& descriptorSetLayouts,
|
||||
fvkmemory::resource_ptr<VulkanProgram> program) {
|
||||
PipelineLayoutKey key = {};
|
||||
uint8_t descSetLayoutCount = 0;
|
||||
key.descSetLayouts = descriptorSetLayouts;
|
||||
@@ -40,13 +39,13 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
for (uint8_t i = 0; i < pushConstantRangeCount; ++i) {
|
||||
auto const& range = pushConstantRanges[i];
|
||||
auto& pushConstant = key.pushConstant[i];
|
||||
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
|
||||
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::VERTEX);
|
||||
}
|
||||
if (range.stageFlags & VK_SHADER_STAGE_FRAGMENT_BIT) {
|
||||
if (range.stageFlags & VK_SHADER_STAGE_FRAGMENT_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::FRAGMENT);
|
||||
}
|
||||
if (range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) {
|
||||
if (range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::COMPUTE);
|
||||
}
|
||||
pushConstant.size = range.size;
|
||||
|
||||
@@ -30,7 +30,7 @@ class VulkanPipelineLayoutCache {
|
||||
public:
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
|
||||
VulkanPipelineLayoutCache(VkDevice device, VulkanResourceAllocator* allocator)
|
||||
VulkanPipelineLayoutCache(VkDevice device)
|
||||
: mDevice(device),
|
||||
mTimestamp(0) {}
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
// A pipeline layout depends on the descriptor set layout and the push constant ranges, which
|
||||
// are described in the program.
|
||||
VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts,
|
||||
VulkanProgram* program);
|
||||
fvkmemory::resource_ptr<VulkanProgram> program);
|
||||
|
||||
private:
|
||||
using Timestamp = uint64_t;
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 "vulkan/memory/Resource.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
|
||||
#include "vulkan/VulkanHandles.h"
|
||||
|
||||
namespace filament::backend::fvkmemory {
|
||||
|
||||
template ResourceType getTypeEnum<VulkanBufferObject>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanIndexBuffer>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanProgram>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanRenderTarget>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanSwapChain>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanRenderPrimitive>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanTexture>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanTextureState>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanTimerQuery>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanVertexBuffer>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanVertexBufferInfo>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanDescriptorSetLayout>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanDescriptorSet>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanFence>() noexcept;
|
||||
|
||||
template<typename D>
|
||||
ResourceType getTypeEnum() noexcept {
|
||||
if constexpr (std::is_same_v<D, VulkanBufferObject>) {
|
||||
return ResourceType::BUFFER_OBJECT;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanIndexBuffer>) {
|
||||
return ResourceType::INDEX_BUFFER;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanProgram>) {
|
||||
return ResourceType::PROGRAM;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanRenderTarget>) {
|
||||
return ResourceType::RENDER_TARGET;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanSwapChain>) {
|
||||
return ResourceType::SWAP_CHAIN;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanRenderPrimitive>) {
|
||||
return ResourceType::RENDER_PRIMITIVE;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanTexture>) {
|
||||
return ResourceType::TEXTURE;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanTextureState>) {
|
||||
return ResourceType::TEXTURE_STATE;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanTimerQuery>) {
|
||||
return ResourceType::TIMER_QUERY;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanVertexBuffer>) {
|
||||
return ResourceType::VERTEX_BUFFER;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanVertexBufferInfo>) {
|
||||
return ResourceType::VERTEX_BUFFER_INFO;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanDescriptorSetLayout>) {
|
||||
return ResourceType::DESCRIPTOR_SET_LAYOUT;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanDescriptorSet>) {
|
||||
return ResourceType::DESCRIPTOR_SET;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanFence>) {
|
||||
return ResourceType::FENCE;
|
||||
}
|
||||
return ResourceType::UNDEFINED_TYPE;
|
||||
}
|
||||
|
||||
std::string getTypeStr(ResourceType type) {
|
||||
switch (type) {
|
||||
case ResourceType::BUFFER_OBJECT:
|
||||
return "BufferObject";
|
||||
case ResourceType::INDEX_BUFFER:
|
||||
return "IndexBuffer";
|
||||
case ResourceType::PROGRAM:
|
||||
return "Program";
|
||||
case ResourceType::RENDER_TARGET:
|
||||
return "RenderTarget";
|
||||
case ResourceType::SWAP_CHAIN:
|
||||
return "SwapChain";
|
||||
case ResourceType::RENDER_PRIMITIVE:
|
||||
return "RenderPrimitive";
|
||||
case ResourceType::TEXTURE:
|
||||
return "Texture";
|
||||
case ResourceType::TEXTURE_STATE:
|
||||
return "TextureState";
|
||||
case ResourceType::TIMER_QUERY:
|
||||
return "TimerQuery";
|
||||
case ResourceType::VERTEX_BUFFER:
|
||||
return "VertexBuffer";
|
||||
case ResourceType::VERTEX_BUFFER_INFO:
|
||||
return "VertexBufferInfo";
|
||||
case ResourceType::DESCRIPTOR_SET_LAYOUT:
|
||||
return "DescriptorSetLayout";
|
||||
case ResourceType::DESCRIPTOR_SET:
|
||||
return "DescriptorSet";
|
||||
case ResourceType::FENCE:
|
||||
return "Fence";
|
||||
case ResourceType::UNDEFINED_TYPE:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::destroy(ResourceType type, HandleId id) {
|
||||
resManager->destructLaterWithType(type, id);
|
||||
|
||||
}
|
||||
void ThreadSafeResource::destroy(ResourceType type, HandleId id) {
|
||||
resManager->destructLaterWithType(type, id);
|
||||
}
|
||||
|
||||
} // namespace filament::backend::fvkmemory
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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_VULKAN_MEMORY_RESOURCE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCE_H
|
||||
|
||||
#include <private/backend/HandleAllocator.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace filament::backend::fvkmemory {
|
||||
|
||||
using CounterIndex = int32_t;
|
||||
using HandleId = HandleBase::HandleId;
|
||||
|
||||
class ResourceManager;
|
||||
|
||||
template <typename D>
|
||||
struct resource_ptr;
|
||||
|
||||
// Subclasses of VulkanResource must provide this enum in their construction.
|
||||
enum class ResourceType : uint8_t {
|
||||
BUFFER_OBJECT = 0,
|
||||
INDEX_BUFFER = 1,
|
||||
PROGRAM = 2,
|
||||
RENDER_TARGET = 3,
|
||||
SWAP_CHAIN = 4,
|
||||
RENDER_PRIMITIVE = 5,
|
||||
TEXTURE = 6,
|
||||
TEXTURE_STATE = 7,
|
||||
TIMER_QUERY = 8,
|
||||
VERTEX_BUFFER = 9,
|
||||
VERTEX_BUFFER_INFO = 10,
|
||||
DESCRIPTOR_SET_LAYOUT = 11,
|
||||
DESCRIPTOR_SET = 12,
|
||||
FENCE = 13,
|
||||
UNDEFINED_TYPE = 14, // Must be the last enum because we use it for iterating over the enums.
|
||||
};
|
||||
|
||||
template<typename D>
|
||||
ResourceType getTypeEnum() noexcept;
|
||||
|
||||
std::string getTypeStr(ResourceType type);
|
||||
|
||||
inline bool isThreadSafeType(ResourceType type) {
|
||||
return type == ResourceType::FENCE || type == ResourceType::TIMER_QUERY;
|
||||
}
|
||||
|
||||
struct Resource {
|
||||
Resource()
|
||||
: resManager(nullptr),
|
||||
id(HandleBase::nullid),
|
||||
mCount(0),
|
||||
restype(ResourceType::UNDEFINED_TYPE) {}
|
||||
|
||||
private:
|
||||
inline void inc() noexcept {
|
||||
mCount++;
|
||||
}
|
||||
|
||||
inline void dec() noexcept {
|
||||
assert_invariant(mCount > 0);
|
||||
if (--mCount == 0) {
|
||||
destroy(restype, id);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void init(HandleId id, ResourceManager* resManager) {
|
||||
this->id = id;
|
||||
this->resManager = resManager;
|
||||
this->restype = getTypeEnum<T>();
|
||||
}
|
||||
|
||||
void destroy(ResourceType type, HandleId id);
|
||||
|
||||
ResourceManager* resManager; // 8
|
||||
HandleId id; // 4
|
||||
uint32_t mCount : 24;
|
||||
ResourceType restype : 6; // restype + mCount is 4 bytes.
|
||||
|
||||
friend class ResourceManager;
|
||||
|
||||
template <typename D>
|
||||
friend struct resource_ptr;
|
||||
};
|
||||
|
||||
struct ThreadSafeResource {
|
||||
ThreadSafeResource()
|
||||
: resManager(nullptr),
|
||||
id(HandleBase::nullid),
|
||||
mCount(0),
|
||||
restype(ResourceType::UNDEFINED_TYPE) {}
|
||||
|
||||
private:
|
||||
inline void inc() noexcept {
|
||||
mCount.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
inline void dec() noexcept {
|
||||
if (mCount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||
destroy(restype, id);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void init(HandleId id, ResourceManager* resManager) {
|
||||
this->id = id;
|
||||
this->resManager = resManager;
|
||||
this->restype = getTypeEnum<T>();
|
||||
}
|
||||
|
||||
void destroy(ResourceType type, HandleId id);
|
||||
|
||||
ResourceManager* resManager; // 8
|
||||
HandleId id; // 4
|
||||
std::atomic<uint32_t> mCount; // 4
|
||||
ResourceType restype; // 1
|
||||
|
||||
friend class ResourceManager;
|
||||
|
||||
template <typename D>
|
||||
friend struct resource_ptr;
|
||||
};
|
||||
|
||||
} // namespace filament::backend::fvkmemory
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCE_H
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/VulkanHandles.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
namespace filament::backend::fvkmemory {
|
||||
|
||||
namespace {
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
uint32_t COUNTER[(size_t) ResourceType::UNDEFINED_TYPE] = {};
|
||||
#endif
|
||||
}
|
||||
|
||||
ResourceManager::ResourceManager(size_t arenaSize, bool disableUseAfterFreeCheck)
|
||||
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck) {}
|
||||
|
||||
void ResourceManager::gc() noexcept {
|
||||
auto destroyAll = [this](GcList& list) {
|
||||
for (auto const& [type, id]: list) {
|
||||
destroyWithType(type, id);
|
||||
}
|
||||
list.clear();
|
||||
};
|
||||
|
||||
{
|
||||
// Note that we're not copying mThreadSafeGcList because the objects here do not have
|
||||
// resource_ptrs to other handle objects, so their desctruction would not add more elements
|
||||
// to mThreadSafeGcList.
|
||||
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
|
||||
destroyAll(mThreadSafeGcList);
|
||||
}
|
||||
|
||||
GcList gcs;
|
||||
std::swap(gcs, mGcList);
|
||||
destroyAll(gcs);
|
||||
}
|
||||
|
||||
void ResourceManager::terminate() noexcept {
|
||||
while (!mThreadSafeGcList.empty() || !mGcList.empty()) {
|
||||
gc();
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
|
||||
switch (type) {
|
||||
case ResourceType::BUFFER_OBJECT:
|
||||
destruct<VulkanBufferObject>(Handle<VulkanBufferObject>(id));
|
||||
break;
|
||||
case ResourceType::INDEX_BUFFER:
|
||||
destruct<VulkanIndexBuffer>(Handle<VulkanIndexBuffer>(id));
|
||||
break;
|
||||
case ResourceType::PROGRAM:
|
||||
destruct<VulkanProgram>(Handle<VulkanProgram>(id));
|
||||
break;
|
||||
case ResourceType::RENDER_TARGET:
|
||||
destruct<VulkanRenderTarget>(Handle<VulkanRenderTarget>(id));
|
||||
break;
|
||||
case ResourceType::SWAP_CHAIN:
|
||||
destruct<VulkanSwapChain>(Handle<VulkanSwapChain>(id));
|
||||
break;
|
||||
case ResourceType::RENDER_PRIMITIVE:
|
||||
destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
|
||||
break;
|
||||
case ResourceType::TEXTURE:
|
||||
destruct<VulkanTexture>(Handle<VulkanTexture>(id));
|
||||
break;
|
||||
case ResourceType::TEXTURE_STATE:
|
||||
destruct<VulkanTextureState>(Handle<VulkanTextureState>(id));
|
||||
break;
|
||||
case ResourceType::TIMER_QUERY:
|
||||
destruct<VulkanTimerQuery>(Handle<VulkanTimerQuery>(id));
|
||||
break;
|
||||
case ResourceType::VERTEX_BUFFER:
|
||||
destruct<VulkanVertexBuffer>(Handle<VulkanVertexBuffer>(id));
|
||||
break;
|
||||
case ResourceType::VERTEX_BUFFER_INFO:
|
||||
destruct<VulkanVertexBufferInfo>(Handle<VulkanVertexBufferInfo>(id));
|
||||
break;
|
||||
case ResourceType::DESCRIPTOR_SET_LAYOUT:
|
||||
destruct<VulkanDescriptorSetLayout>(Handle<VulkanDescriptorSetLayout>(id));
|
||||
break;
|
||||
case ResourceType::DESCRIPTOR_SET:
|
||||
destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
|
||||
break;
|
||||
case ResourceType::FENCE:
|
||||
destruct<VulkanFence>(Handle<VulkanFence>(id));
|
||||
break;
|
||||
case ResourceType::UNDEFINED_TYPE:
|
||||
break;
|
||||
}
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
COUNTER[(size_t) type]--;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ResourceManager::traceConstruction(ResourceType type, HandleId id) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
assert_invariant(type != ResourceType::UNDEFINED_TYPE);
|
||||
COUNTER[(size_t) type]++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ResourceManager::print() const noexcept {
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
utils::slog.e << "-------------------" << utils::io::endl;
|
||||
for (size_t i = 0; i < (size_t) ResourceType::UNDEFINED_TYPE; ++i) {
|
||||
utils::slog.e <<" " << getTypeStr((ResourceType) i) << "=" << COUNTER[i] << utils::io::endl;
|
||||
}
|
||||
utils::slog.e << "+++++++++++++++++++" << utils::io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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_VULKAN_MEMORY_RESOURCEMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEMANAGER_H
|
||||
|
||||
#include "vulkan/memory/Resource.h"
|
||||
|
||||
#include "vulkan/VulkanAsyncHandles.h"
|
||||
|
||||
#include <private/backend/HandleAllocator.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
namespace filament::backend::fvkmemory {
|
||||
|
||||
class ResourceManager {
|
||||
public:
|
||||
ResourceManager(size_t arenaSize, bool disableUseAfterFreeCheck);
|
||||
|
||||
template<typename D>
|
||||
inline Handle<D> allocHandle() noexcept {
|
||||
return mHandleAllocatorImpl.allocate<D>();
|
||||
}
|
||||
|
||||
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
|
||||
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
|
||||
}
|
||||
|
||||
void gc() noexcept;
|
||||
void print() const noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
using AllocatorImpl = HandleAllocatorVK;
|
||||
|
||||
template<typename D>
|
||||
using requires_thread_safety = typename std::disjunction<std::is_same<D, VulkanFence>,
|
||||
std::is_same<D, VulkanTimerQuery>>;
|
||||
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
inline D* construct(Handle<B> const& handle, ARGS&&... args) noexcept {
|
||||
constexpr bool THREAD_SAFETY = requires_thread_safety<D>::value;
|
||||
D* obj = mHandleAllocatorImpl.construct<D, B>(handle, std::forward<ARGS>(args)...);
|
||||
if constexpr (THREAD_SAFETY) {
|
||||
((ThreadSafeResource*) obj)->template init<D>(handle.getId(), this);
|
||||
} else {
|
||||
((Resource*) obj)->template init<D>(handle.getId(), this);
|
||||
}
|
||||
traceConstruction(getTypeEnum<D>(), handle.getId());
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> && std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B> const& handle) noexcept {
|
||||
return mHandleAllocatorImpl.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
template<typename D, typename B>
|
||||
inline void destruct(Handle<B> handle) noexcept {
|
||||
D* obj = handle_cast<D*>(handle);
|
||||
mHandleAllocatorImpl.deallocate(handle, obj);
|
||||
}
|
||||
|
||||
// This will post the destruction to a list that will be processed when gc is called from the
|
||||
// backend thread.
|
||||
inline void destructLaterWithType(ResourceType type, HandleId id) {
|
||||
if (isThreadSafeType(type)) {
|
||||
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
|
||||
mThreadSafeGcList.push_back({type, id});
|
||||
} else {
|
||||
mGcList.push_back({type, id});
|
||||
}
|
||||
}
|
||||
|
||||
void destroyWithType(ResourceType type, HandleId id);
|
||||
|
||||
void traceConstruction(ResourceType type, HandleId id);
|
||||
|
||||
AllocatorImpl mHandleAllocatorImpl;
|
||||
|
||||
using GcList = std::vector<std::pair<ResourceType, HandleId>>;
|
||||
utils::Mutex mThreadSafeGcListMutex;
|
||||
GcList mThreadSafeGcList;
|
||||
GcList mGcList;
|
||||
|
||||
template<typename D>
|
||||
friend struct resource_ptr;
|
||||
|
||||
friend struct Resource;
|
||||
friend struct ThreadSafeResource;
|
||||
};
|
||||
|
||||
} // namespace filament::backend::fvkmemory
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEMANAGER_H
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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_VULKAN_MEMORY_RESOURCEPOINTER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEPOINTER_H
|
||||
|
||||
#include "vulkan/memory/Resource.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace filament::backend {
|
||||
class VulkanDriver;
|
||||
|
||||
namespace fvkmemory {
|
||||
|
||||
// This is a ref-counting, reference holder class (similar to std::shared_ptr) that serves as the
|
||||
// primary interface between the vk backend (VulkanDriver) and the handle allocator. All objects
|
||||
// that are allocated from the handle allocator are expected to be contained within a
|
||||
template<typename D>
|
||||
struct resource_ptr {
|
||||
|
||||
private:
|
||||
template<typename B>
|
||||
using enabled_resource_ptr =
|
||||
resource_ptr<typename std::enable_if<std::is_base_of<B, D>::value, D>::type>;
|
||||
public:
|
||||
template<typename B, typename... ARGS>
|
||||
static enabled_resource_ptr<B> make(ResourceManager* resManager, Handle<B> const& handle,
|
||||
ARGS&&... args) noexcept {
|
||||
D* ptr = resManager->construct<D, B>(handle, std::forward<ARGS>(args)...);
|
||||
return {ptr};
|
||||
}
|
||||
|
||||
// This will alloc a handle and then construct the object.
|
||||
template<typename... ARGS>
|
||||
static resource_ptr<D> construct(ResourceManager* resManager, ARGS&&... args) noexcept {
|
||||
auto handle = resManager->allocHandle<D>();
|
||||
D* ptr = resManager->construct<D, D>(handle, std::forward<ARGS>(args)...);
|
||||
return {ptr};
|
||||
}
|
||||
|
||||
template<typename B>
|
||||
static enabled_resource_ptr<B> cast(ResourceManager* resManager,
|
||||
Handle<B> const& handle) noexcept {
|
||||
D* ptr = resManager->handle_cast<D*, B>(handle);
|
||||
return {ptr};
|
||||
}
|
||||
|
||||
template<typename B>
|
||||
static enabled_resource_ptr<B> cast(B* ptr) noexcept {
|
||||
return {ptr};
|
||||
}
|
||||
|
||||
~resource_ptr() {
|
||||
if (mRef) {
|
||||
mRef->dec();
|
||||
}
|
||||
}
|
||||
|
||||
inline resource_ptr() = default;
|
||||
|
||||
// move constructor operator
|
||||
inline resource_ptr(resource_ptr<D>&& rhs) {
|
||||
(*this) = std::move(rhs);
|
||||
}
|
||||
|
||||
inline resource_ptr(resource_ptr<D> const& rhs) {
|
||||
(*this) = rhs;
|
||||
}
|
||||
|
||||
template<typename E>
|
||||
using is_supported_t =
|
||||
typename std::enable_if<std::is_base_of<D, E>::value>::type;
|
||||
template<typename E, typename = is_supported_t<E>>
|
||||
inline resource_ptr(resource_ptr<E> const& rhs) {
|
||||
(*this) = rhs;
|
||||
}
|
||||
|
||||
// move operator
|
||||
inline resource_ptr<D>& operator=(resource_ptr<D> && rhs) {
|
||||
std::swap(mRef, rhs.mRef);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline resource_ptr<D>& operator=(resource_ptr<D> const& rhs) {
|
||||
if (mRef == rhs.mRef) {
|
||||
return *this;
|
||||
}
|
||||
if (mRef) {
|
||||
mRef->dec();
|
||||
}
|
||||
mRef = rhs.mRef;
|
||||
mRef->inc();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename E, typename = is_supported_t<E>>
|
||||
inline resource_ptr<D>& operator=(resource_ptr<E> const& rhs) {
|
||||
if (mRef == rhs.mRef) {
|
||||
return *this;
|
||||
}
|
||||
if (mRef) {
|
||||
mRef->dec();
|
||||
}
|
||||
mRef = rhs.mRef;
|
||||
mRef->inc();
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool operator==(resource_ptr<D> const& other) const {
|
||||
return id() == other.id() && type() == other.type();
|
||||
}
|
||||
|
||||
inline explicit operator bool() const {
|
||||
return bool(mRef);
|
||||
}
|
||||
|
||||
inline D* operator->() {
|
||||
return get();
|
||||
}
|
||||
|
||||
inline D const* operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
inline HandleId id() const {
|
||||
if (mRef) {
|
||||
return mRef->id;
|
||||
}
|
||||
return HandleBase::nullid;
|
||||
}
|
||||
|
||||
inline D* get() const {
|
||||
assert_invariant(mRef);
|
||||
return mRef;
|
||||
}
|
||||
|
||||
private:
|
||||
// inc() and dec() For tracking ref-count with respect to create/destroy backend APIs. They can
|
||||
// only be used from VulkanDriver.
|
||||
inline void dec() {
|
||||
assert_invariant(mRef);
|
||||
mRef->dec();
|
||||
}
|
||||
|
||||
inline void inc() {
|
||||
assert_invariant(mRef);
|
||||
mRef->inc();
|
||||
}
|
||||
|
||||
inline ResourceType type() const {
|
||||
if (mRef) {
|
||||
return mRef->restype;
|
||||
}
|
||||
return ResourceType::UNDEFINED_TYPE;
|
||||
}
|
||||
|
||||
resource_ptr(D* ref)
|
||||
: mRef(ref) {
|
||||
mRef->inc();
|
||||
}
|
||||
|
||||
D* mRef = nullptr;
|
||||
|
||||
// Allow generic structures to hold resources of different types. For example,
|
||||
// std::vector<fvkmemory::Resource> resources;
|
||||
friend struct resource_ptr<Resource>;
|
||||
friend struct resource_ptr<ThreadSafeResource>;
|
||||
|
||||
// This enables access to resource_ptr's private inc() and dec() methods.
|
||||
friend class filament::backend::VulkanDriver;
|
||||
};
|
||||
|
||||
} // namespace fvkmemory
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEPOINTER_H
|
||||
@@ -300,10 +300,10 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
}
|
||||
|
||||
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
VkPhysicalDeviceFeatures const& features, uint32_t graphicsQueueFamilyIndex,
|
||||
ExtensionSet const& deviceExtensions) {
|
||||
VkPhysicalDeviceFeatures2 const& features, uint32_t graphicsQueueFamilyIndex,
|
||||
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions) {
|
||||
VkDevice device;
|
||||
VkDeviceQueueCreateInfo deviceQueueCreateInfo[1] = {};
|
||||
VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {};
|
||||
const float queuePriority[] = {1.0f};
|
||||
VkDeviceCreateInfo deviceCreateInfo = {};
|
||||
FixedCapacityVector<const char*> requestExtensions;
|
||||
@@ -314,22 +314,30 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
for (auto const& ext: deviceExtensions) {
|
||||
requestExtensions.push_back(ext.data());
|
||||
}
|
||||
deviceQueueCreateInfo->sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
deviceQueueCreateInfo->queueFamilyIndex = graphicsQueueFamilyIndex;
|
||||
deviceQueueCreateInfo->queueCount = 1;
|
||||
deviceQueueCreateInfo->pQueuePriorities = &queuePriority[0];
|
||||
deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
deviceQueueCreateInfo[0].queueFamilyIndex = graphicsQueueFamilyIndex;
|
||||
deviceQueueCreateInfo[0].queueCount = 1;
|
||||
deviceQueueCreateInfo[0].pQueuePriorities = &queuePriority[0];
|
||||
// Protected queue
|
||||
deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
deviceQueueCreateInfo[1].flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
|
||||
deviceQueueCreateInfo[1].queueFamilyIndex = protectedGraphicsQueueFamilyIndex;
|
||||
deviceQueueCreateInfo[1].queueCount = 1;
|
||||
deviceQueueCreateInfo[1].pQueuePriorities = &queuePriority[0];
|
||||
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.queueCreateInfoCount = 1;
|
||||
bool const hasProtectedQueue = protectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX;
|
||||
deviceCreateInfo.queueCreateInfoCount = hasProtectedQueue ? 2 : 1;
|
||||
deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo;
|
||||
|
||||
// We could simply enable all supported features, but since that may have performance
|
||||
// consequences let's just enable the features we need.
|
||||
VkPhysicalDeviceFeatures enabledFeatures{
|
||||
.depthClamp = features.depthClamp,
|
||||
.samplerAnisotropy = features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.textureCompressionBC,
|
||||
.shaderClipDistance = features.shaderClipDistance,
|
||||
.depthClamp = features.features.depthClamp,
|
||||
.samplerAnisotropy = features.features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.features.textureCompressionBC,
|
||||
.shaderClipDistance = features.features.shaderClipDistance,
|
||||
};
|
||||
|
||||
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
||||
@@ -360,6 +368,16 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
pNext = &multiview;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceProtectedMemoryFeatures protectedMemory = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
|
||||
};
|
||||
if (hasProtectedQueue) {
|
||||
// Enable protected memory, if requested.
|
||||
protectedMemory.protectedMemory = VK_TRUE;
|
||||
protectedMemory.pNext = pNext;
|
||||
pNext = &protectedMemory;
|
||||
}
|
||||
|
||||
deviceCreateInfo.pNext = pNext;
|
||||
|
||||
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, VKALLOC, &device);
|
||||
@@ -406,13 +424,13 @@ FixedCapacityVector<VkQueueFamilyProperties> getPhysicalDeviceQueueFamilyPropert
|
||||
return queueFamiliesProperties;
|
||||
}
|
||||
|
||||
uint32_t identifyGraphicsQueueFamilyIndex(VkPhysicalDevice physicalDevice) {
|
||||
uint32_t identifyGraphicsQueueFamilyIndex(VkPhysicalDevice physicalDevice, VkQueueFlags flags) {
|
||||
const FixedCapacityVector<VkQueueFamilyProperties> queueFamiliesProperties
|
||||
= getPhysicalDeviceQueueFamilyPropertiesHelper(physicalDevice);
|
||||
uint32_t graphicsQueueFamilyIndex = INVALID_VK_INDEX;
|
||||
for (uint32_t j = 0; j < queueFamiliesProperties.size(); ++j) {
|
||||
VkQueueFamilyProperties props = queueFamiliesProperties[j];
|
||||
if (props.queueCount != 0 && props.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||
if (props.queueCount != 0 && props.queueFlags & flags) {
|
||||
graphicsQueueFamilyIndex = j;
|
||||
break;
|
||||
}
|
||||
@@ -473,7 +491,8 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
// Does the device have any command queues that support graphics?
|
||||
// In theory, we should also ensure that the device supports presentation of our
|
||||
// particular VkSurface, but we don't have a VkSurface yet, so we'll skip this requirement.
|
||||
if (identifyGraphicsQueueFamilyIndex(candidateDevice) == INVALID_VK_INDEX) {
|
||||
if (identifyGraphicsQueueFamilyIndex(candidateDevice, VK_QUEUE_GRAPHICS_BIT)
|
||||
== INVALID_VK_INDEX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -584,6 +603,9 @@ struct VulkanPlatformPrivate {
|
||||
uint32_t mGraphicsQueueFamilyIndex = INVALID_VK_INDEX;
|
||||
uint32_t mGraphicsQueueIndex = INVALID_VK_INDEX;
|
||||
VkQueue mGraphicsQueue = VK_NULL_HANDLE;
|
||||
uint32_t mProtectedGraphicsQueueFamilyIndex = INVALID_VK_INDEX;
|
||||
uint32_t mProtectedGraphicsQueueIndex = INVALID_VK_INDEX;
|
||||
VkQueue mProtectedGraphicsQueue = VK_NULL_HANDLE;
|
||||
VulkanContext mContext = {};
|
||||
|
||||
// We use a map to both map a handle (i.e. SwapChainPtr) to the concrete type and also to
|
||||
@@ -686,22 +708,45 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
|
||||
printDeviceInfo(mImpl->mInstance, mImpl->mPhysicalDevice);
|
||||
|
||||
VkPhysicalDeviceProtectedMemoryFeatures queryProtectedMemoryFeatures = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
|
||||
};
|
||||
// Chain to the pNext linked list
|
||||
queryProtectedMemoryFeatures.pNext = context.mPhysicalDeviceFeatures.pNext;
|
||||
context.mPhysicalDeviceFeatures.pNext = &queryProtectedMemoryFeatures;
|
||||
VkPhysicalDeviceProtectedMemoryProperties protectedMemoryProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
|
||||
};
|
||||
protectedMemoryProperties.pNext = context.mPhysicalDeviceProperties.pNext;
|
||||
context.mPhysicalDeviceProperties.pNext = &protectedMemoryProperties;
|
||||
|
||||
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
||||
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
|
||||
vkGetPhysicalDeviceProperties(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
||||
vkGetPhysicalDeviceFeatures(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
|
||||
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
||||
vkGetPhysicalDeviceFeatures2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
|
||||
vkGetPhysicalDeviceMemoryProperties(mImpl->mPhysicalDevice, &context.mMemoryProperties);
|
||||
|
||||
mImpl->mGraphicsQueueFamilyIndex
|
||||
= mImpl->mGraphicsQueueFamilyIndex == INVALID_VK_INDEX
|
||||
? identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice)
|
||||
? identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice,
|
||||
VK_QUEUE_GRAPHICS_BIT)
|
||||
: mImpl->mGraphicsQueueFamilyIndex;
|
||||
assert_invariant(mImpl->mGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
|
||||
|
||||
// We know we need to allocate the protected version of the VK objects
|
||||
context.mProtectedMemorySupported =
|
||||
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
|
||||
if (context.mProtectedMemorySupported) {
|
||||
mImpl->mProtectedGraphicsQueueFamilyIndex
|
||||
= identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice,
|
||||
(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
|
||||
assert_invariant(mImpl->mProtectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
|
||||
}
|
||||
|
||||
// Only enable shaderClipDistance if we are doing instanced stereoscopic rendering.
|
||||
if (context.mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE
|
||||
if (context.mPhysicalDeviceFeatures.features.shaderClipDistance == VK_TRUE
|
||||
&& driverConfig.stereoscopicType != StereoscopicType::INSTANCED) {
|
||||
context.mPhysicalDeviceFeatures.shaderClipDistance = VK_FALSE;
|
||||
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
|
||||
}
|
||||
|
||||
// At this point, we should have a family index that points to a family that has > 0 queues for
|
||||
@@ -711,6 +756,11 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
mImpl->mGraphicsQueueIndex
|
||||
= mImpl->mGraphicsQueueIndex == INVALID_VK_INDEX ? 0 : mImpl->mGraphicsQueueIndex;
|
||||
|
||||
// Applying the same logic to the protected queue index (Not sure about shared context and protection)
|
||||
mImpl->mProtectedGraphicsQueueIndex
|
||||
= mImpl->mProtectedGraphicsQueueIndex == INVALID_VK_INDEX ? 0 :
|
||||
mImpl->mProtectedGraphicsQueueIndex;
|
||||
|
||||
ExtensionSet deviceExts;
|
||||
// If using a shared context, we do not assume any extensions.
|
||||
if (!mImpl->mSharedContext) {
|
||||
@@ -727,7 +777,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
|
||||
mImpl->mDevice
|
||||
= mImpl->mDevice == VK_NULL_HANDLE ? createLogicalDevice(mImpl->mPhysicalDevice,
|
||||
context.mPhysicalDeviceFeatures, mImpl->mGraphicsQueueFamilyIndex, deviceExts)
|
||||
context.mPhysicalDeviceFeatures, mImpl->mGraphicsQueueFamilyIndex,
|
||||
mImpl->mProtectedGraphicsQueueFamilyIndex, deviceExts)
|
||||
: mImpl->mDevice;
|
||||
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
|
||||
|
||||
@@ -735,6 +786,15 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
&mImpl->mGraphicsQueue);
|
||||
assert_invariant(mImpl->mGraphicsQueue != VK_NULL_HANDLE);
|
||||
|
||||
if (context.mProtectedMemorySupported) {
|
||||
VkDeviceQueueInfo2 info = {};
|
||||
info.queueFamilyIndex = mImpl->mProtectedGraphicsQueueFamilyIndex;
|
||||
info.queueIndex = mImpl->mProtectedGraphicsQueueIndex;
|
||||
info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2;
|
||||
info.flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
|
||||
vkGetDeviceQueue2(mImpl->mDevice, &info, &mImpl->mProtectedGraphicsQueue);
|
||||
}
|
||||
|
||||
// Store the extension support in the context
|
||||
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
@@ -800,6 +860,10 @@ bool VulkanPlatform::hasResized(SwapChainPtr handle) {
|
||||
SWAPCHAIN_RET_FUNC(hasResized, handle, )
|
||||
}
|
||||
|
||||
bool VulkanPlatform::isProtected(SwapChainPtr handle) {
|
||||
SWAPCHAIN_RET_FUNC(isProtected, handle, )
|
||||
}
|
||||
|
||||
VkResult VulkanPlatform::recreate(SwapChainPtr handle) {
|
||||
SWAPCHAIN_RET_FUNC(recreate, handle, )
|
||||
}
|
||||
@@ -829,6 +893,13 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
|
||||
flags |= SWAP_CHAIN_CONFIG_ENABLE_XCB;
|
||||
}
|
||||
|
||||
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
|
||||
if (!mImpl->mContext.mProtectedMemorySupported) {
|
||||
utils::slog.w << "protected swapchain requested, but VulkanPlatform does not support it"
|
||||
<< utils::io::endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto [surface, fallbackExtent] = createVkSurfaceKHR(nativeWindow, mImpl->mInstance, flags);
|
||||
// The VulkanPlatformSurfaceSwapChain now `owns` the surface.
|
||||
VulkanPlatformSurfaceSwapChain* swapchain = new VulkanPlatformSurfaceSwapChain(mImpl->mContext,
|
||||
@@ -862,6 +933,18 @@ VkQueue VulkanPlatform::getGraphicsQueue() const noexcept {
|
||||
return mImpl->mGraphicsQueue;
|
||||
}
|
||||
|
||||
uint32_t VulkanPlatform::getProtectedGraphicsQueueFamilyIndex() const noexcept {
|
||||
return mImpl->mProtectedGraphicsQueueFamilyIndex;
|
||||
}
|
||||
|
||||
uint32_t VulkanPlatform::getProtectedGraphicsQueueIndex() const noexcept {
|
||||
return mImpl->mProtectedGraphicsQueueIndex;
|
||||
}
|
||||
|
||||
VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept {
|
||||
return mImpl->mProtectedGraphicsQueue;
|
||||
}
|
||||
|
||||
#undef SWAPCHAIN_RET_FUNC
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -195,6 +195,15 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
}
|
||||
#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),
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace filament::backend {
|
||||
namespace {
|
||||
|
||||
std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& context,
|
||||
VkDevice device, VkExtent2D extent, VkFormat format) {
|
||||
VkDevice device, VkExtent2D extent, VkFormat format,
|
||||
bool isProtected) {
|
||||
bool const isDepth = isVkDepthFormat(format);
|
||||
// Filament expects blit() to work with any texture, so we almost always set these usage flags
|
||||
// (see copyFrame() and readPixels()).
|
||||
@@ -38,6 +39,7 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
||||
|
||||
VkImageCreateInfo imageInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.flags = isProtected ? VK_IMAGE_CREATE_PROTECTED_BIT : VkImageCreateFlags(0),
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {extent.width, extent.height, 1},
|
||||
@@ -58,8 +60,11 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetImageMemoryRequirements(device, image, &memReqs);
|
||||
|
||||
const VkFlags requiredMemoryFlags =
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||
(isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
|
||||
uint32_t memoryTypeIndex
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
|
||||
<< "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
|
||||
@@ -110,8 +115,9 @@ void VulkanPlatformSwapChainImpl::destroy() {
|
||||
mSwapChainBundle.colors.clear();
|
||||
}
|
||||
|
||||
VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format) {
|
||||
auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format);
|
||||
VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format,
|
||||
bool isProtected) {
|
||||
auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format, isProtected);
|
||||
mMemory.insert({image, memory});
|
||||
return image;
|
||||
}
|
||||
@@ -125,7 +131,8 @@ VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext con
|
||||
mSurface(surface),
|
||||
mFallbackExtent(fallbackExtent),
|
||||
mUsesRGB((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0),
|
||||
mHasStencil((flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0) {
|
||||
mHasStencil((flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0),
|
||||
mIsProtected((flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) != 0) {
|
||||
assert_invariant(surface);
|
||||
create();
|
||||
}
|
||||
@@ -212,6 +219,8 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
|
||||
VkSwapchainCreateInfoKHR const createInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.flags = mIsProtected ? VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR :
|
||||
VkSwapchainCreateFlagsKHR(0),
|
||||
.surface = mSurface,
|
||||
.minImageCount = desiredImageCount,
|
||||
.imageFormat = surfaceFormat.format,
|
||||
@@ -245,14 +254,17 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
mSwapChainBundle.colorFormat = surfaceFormat.format;
|
||||
mSwapChainBundle.depthFormat =
|
||||
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), mHasStencil);
|
||||
mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat);
|
||||
mSwapChainBundle.depth = createImage(mSwapChainBundle.extent,
|
||||
mSwapChainBundle.depthFormat, mIsProtected);
|
||||
mSwapChainBundle.isProtected = mIsProtected;
|
||||
|
||||
FVK_LOGI << "vkCreateSwapchain"
|
||||
<< ": " << mSwapChainBundle.extent.width << "x" << mSwapChainBundle.extent.height << ", "
|
||||
<< surfaceFormat.format << ", " << surfaceFormat.colorSpace << ", "
|
||||
<< "swapchain-size=" << mSwapChainBundle.colors.size() << ", "
|
||||
<< "identity-transform=" << (caps.currentTransform == 1) << ", "
|
||||
<< "depth=" << mSwapChainBundle.depthFormat
|
||||
<< "depth=" << mSwapChainBundle.depthFormat << ", "
|
||||
<< "protected=" << mSwapChainBundle.isProtected
|
||||
<< io::endl;
|
||||
|
||||
VkSemaphoreCreateInfo const semaphoreCreateInfo = {
|
||||
@@ -317,6 +329,10 @@ bool VulkanPlatformSurfaceSwapChain::hasResized() {
|
||||
return !equivalent(mSwapChainBundle.extent, perceivedExtent);
|
||||
}
|
||||
|
||||
bool VulkanPlatformSurfaceSwapChain::isProtected() {
|
||||
return mIsProtected;
|
||||
}
|
||||
|
||||
// Non-virtual override
|
||||
VkResult VulkanPlatformSurfaceSwapChain::recreate() {
|
||||
destroy();
|
||||
@@ -347,13 +363,13 @@ VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext c
|
||||
images.reserve(HEADLESS_SWAPCHAIN_SIZE);
|
||||
images.resize(HEADLESS_SWAPCHAIN_SIZE);
|
||||
for (size_t i = 0; i < HEADLESS_SWAPCHAIN_SIZE; ++i) {
|
||||
images[i] = createImage(extent, mSwapChainBundle.colorFormat);
|
||||
images[i] = createImage(extent, mSwapChainBundle.colorFormat, false);
|
||||
}
|
||||
|
||||
bool const hasStencil = (flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0;
|
||||
mSwapChainBundle.depthFormat =
|
||||
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), hasStencil);
|
||||
mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat);
|
||||
mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat, false);
|
||||
}
|
||||
|
||||
VulkanPlatformHeadlessSwapChain::~VulkanPlatformHeadlessSwapChain() {
|
||||
|
||||
@@ -64,11 +64,16 @@ struct VulkanPlatformSwapChainImpl : public Platform::SwapChain {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Non-virtual override-able method
|
||||
bool isProtected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Non-virtual override-able method
|
||||
void destroy();
|
||||
|
||||
VkImage createImage(VkExtent2D extent, VkFormat format);
|
||||
VkImage createImage(VkExtent2D extent, VkFormat format, bool isProtected);
|
||||
|
||||
VulkanContext const& mContext;
|
||||
VkDevice mDevice;
|
||||
@@ -97,6 +102,9 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
|
||||
// Non-virtual override-able method
|
||||
bool hasResized();
|
||||
|
||||
// Non-virtual override-able method
|
||||
bool isProtected();
|
||||
|
||||
protected:
|
||||
// Non-virtual override-able method
|
||||
void destroy();
|
||||
@@ -117,6 +125,7 @@ private:
|
||||
|
||||
bool mUsesRGB = false;
|
||||
bool mHasStencil = false;
|
||||
bool mIsProtected = false;
|
||||
bool mSuboptimal;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
@@ -309,6 +313,7 @@ public:
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
* @deprecated use "backend.disable_parallel_shader_compile" feature flag instead
|
||||
*/
|
||||
bool disableParallelShaderCompile = false;
|
||||
|
||||
@@ -349,6 +354,7 @@ public:
|
||||
|
||||
/*
|
||||
* Disable backend handles use-after-free checks.
|
||||
* @deprecated use "backend.disable_handle_use_after_free_check" feature flag instead
|
||||
*/
|
||||
bool disableHandleUseAfterFreeCheck = false;
|
||||
|
||||
@@ -385,10 +391,32 @@ public:
|
||||
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
|
||||
* This is only supported for:
|
||||
* - PlatformEGLAndroid
|
||||
* @deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Feature flags can be enabled or disabled when the Engine is built. Some Feature flags can
|
||||
* also be toggled at any time. Feature flags should alawys use their default value unless
|
||||
* the feature enabled by the flag is faulty. Feature flags provide a last resort way to
|
||||
* disable problematic features.
|
||||
* Feature flags are intended to have a short life-time and are regularly removed as features
|
||||
* mature.
|
||||
*/
|
||||
struct FeatureFlag {
|
||||
char const* UTILS_NONNULL name; //!< name of the feature flag
|
||||
char const* UTILS_NONNULL description; //!< short description
|
||||
bool const* UTILS_NONNULL value; //!< pointer to the value of the flag
|
||||
bool constant; //!< whether the flag is constant after construction
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the list of available feature flags
|
||||
*/
|
||||
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept;
|
||||
|
||||
#if UTILS_HAS_THREADING
|
||||
using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token);
|
||||
#endif
|
||||
@@ -461,6 +489,21 @@ public:
|
||||
*/
|
||||
Builder& paused(bool paused) noexcept;
|
||||
|
||||
/**
|
||||
* Set a feature flag value. This is the only way to set constant feature flags.
|
||||
* @param name feature name
|
||||
* @param value true to enable, false to disable
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
Builder& feature(char const* UTILS_NONNULL name, bool value) noexcept;
|
||||
|
||||
/**
|
||||
* Enables a list of features.
|
||||
* @param list list of feature names to enable.
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
Builder& features(std::initializer_list<char const *> list) noexcept;
|
||||
|
||||
#if UTILS_HAS_THREADING
|
||||
/**
|
||||
* Creates the filament Engine asynchronously.
|
||||
@@ -1078,6 +1121,37 @@ public:
|
||||
|
||||
DebugRegistry& getDebugRegistry() noexcept;
|
||||
|
||||
/**
|
||||
* Check if a feature flag exists
|
||||
* @param name name of the feature flag to check
|
||||
* @return true if the feature flag exists, false otherwise
|
||||
*/
|
||||
inline bool hasFeatureFlag(char const* UTILS_NONNULL name) noexcept {
|
||||
return getFeatureFlag(name).has_value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a non-constant feature flag.
|
||||
* @param name name of the feature flag to set
|
||||
* @param value value to set
|
||||
* @return true if the value was set, false if the feature flag is constant or doesn't exist.
|
||||
*/
|
||||
bool setFeatureFlag(char const* UTILS_NONNULL name, bool value) noexcept;
|
||||
|
||||
/**
|
||||
* Retrieves the value of any feature flag.
|
||||
* @param name name of the feature flag
|
||||
* @return the value of the flag if it exists
|
||||
*/
|
||||
std::optional<bool> getFeatureFlag(char const* UTILS_NONNULL name) const noexcept;
|
||||
|
||||
/**
|
||||
* Returns a pointer to a non-constant feature flag value.
|
||||
* @param name name of the feature flag
|
||||
* @return a pointer to the feature flag value, or nullptr if the feature flag is constant or doesn't exist
|
||||
*/
|
||||
bool* UTILS_NULLABLE getFeatureFlagPtr(char const* UTILS_NONNULL name) const noexcept;
|
||||
|
||||
protected:
|
||||
//! \privatesection
|
||||
Engine() noexcept = default;
|
||||
|
||||
@@ -668,6 +668,26 @@ public:
|
||||
*/
|
||||
bool isFrontFaceWindingInverted() const noexcept;
|
||||
|
||||
/**
|
||||
* Enables or disables transparent picking. Disabled by default.
|
||||
*
|
||||
* When transparent picking is enabled, View::pick() will pick from both
|
||||
* transparent and opaque renderables. When disabled, View::pick() will only
|
||||
* pick from opaque renderables.
|
||||
*
|
||||
* @param enabled true enables transparent picking, false disables it.
|
||||
*
|
||||
* @note Transparent picking will create an extra pass for rendering depth
|
||||
* from both transparent and opaque renderables.
|
||||
*/
|
||||
void setTransparentPickingEnabled(bool enabled) noexcept;
|
||||
|
||||
/**
|
||||
* Returns true if transparent picking is enabled.
|
||||
* See setTransparentPickingEnabled() for more information.
|
||||
*/
|
||||
bool isTransparentPickingEnabled() const noexcept;
|
||||
|
||||
/**
|
||||
* Enables use of the stencil buffer.
|
||||
*
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -442,6 +443,22 @@ uint64_t Engine::getSteadyClockTimeNano() noexcept {
|
||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
utils::Slice<const Engine::FeatureFlag> Engine::getFeatureFlags() const noexcept {
|
||||
return downcast(this)->getFeatureFlags();
|
||||
}
|
||||
|
||||
bool Engine::setFeatureFlag(char const* name, bool value) noexcept {
|
||||
return downcast(this)->setFeatureFlag(name, value);
|
||||
}
|
||||
|
||||
std::optional<bool> Engine::getFeatureFlag(char const* name) const noexcept {
|
||||
return downcast(this)->getFeatureFlag(name);
|
||||
}
|
||||
|
||||
bool* Engine::getFeatureFlagPtr(char const* UTILS_NONNULL name) const noexcept {
|
||||
return downcast(this)->getFeatureFlagPtr(name);
|
||||
}
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
void Engine::resetBackendState() noexcept {
|
||||
downcast(this)->resetBackendState();
|
||||
|
||||
+302
-235
File diff suppressed because it is too large
Load Diff
@@ -21,11 +21,12 @@
|
||||
|
||||
#include "FrameHistory.h"
|
||||
|
||||
#include "ds/DescriptorSetLayout.h"
|
||||
#include "ds/PostProcessDescriptorSet.h"
|
||||
#include "ds/SsrPassDescriptorSet.h"
|
||||
#include "ds/TypedUniformBuffer.h"
|
||||
|
||||
#include <private/filament/Variant.h>
|
||||
|
||||
#include <fg/FrameGraphId.h>
|
||||
#include <fg/FrameGraphResources.h>
|
||||
#include <fg/FrameGraphTexture.h>
|
||||
@@ -33,25 +34,23 @@
|
||||
#include <filament/Options.h>
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <private/filament/EngineEnums.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/PipelineState.h>
|
||||
|
||||
#include <private/filament/Variant.h>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <initializer_list>
|
||||
#include <random>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -71,16 +70,19 @@ struct CameraInfo;
|
||||
class PostProcessManager {
|
||||
public:
|
||||
|
||||
struct ConstantInfo {
|
||||
std::string_view name;
|
||||
std::variant<int32_t, float, bool> value;
|
||||
};
|
||||
|
||||
struct MaterialInfo {
|
||||
// This is intended to be used only to hold the static material data
|
||||
struct StaticMaterialInfo {
|
||||
struct ConstantInfo {
|
||||
std::string_view name;
|
||||
std::variant<int32_t, float, bool> value;
|
||||
};
|
||||
std::string_view name;
|
||||
uint8_t const* data;
|
||||
int size;
|
||||
utils::FixedCapacityVector<ConstantInfo> constants = {};
|
||||
size_t size;
|
||||
// the life-time of objects pointed to by this initializer_list<> is extended to the
|
||||
// life-time of the initializer_list
|
||||
std::initializer_list<ConstantInfo> constants;
|
||||
};
|
||||
|
||||
struct ColorGradingConfig {
|
||||
@@ -320,8 +322,7 @@ public:
|
||||
|
||||
class PostProcessMaterial {
|
||||
public:
|
||||
PostProcessMaterial() noexcept;
|
||||
PostProcessMaterial(MaterialInfo const& info) noexcept;
|
||||
explicit PostProcessMaterial(StaticMaterialInfo const& info) noexcept;
|
||||
|
||||
PostProcessMaterial(PostProcessMaterial const& rhs) = delete;
|
||||
PostProcessMaterial& operator=(PostProcessMaterial const& rhs) = delete;
|
||||
@@ -329,15 +330,21 @@ public:
|
||||
PostProcessMaterial(PostProcessMaterial&& rhs) noexcept;
|
||||
PostProcessMaterial& operator=(PostProcessMaterial&& rhs) noexcept;
|
||||
|
||||
~PostProcessMaterial();
|
||||
~PostProcessMaterial() noexcept;
|
||||
|
||||
void terminate(FEngine& engine) noexcept;
|
||||
|
||||
FMaterial* getMaterial(FEngine& engine) const noexcept;
|
||||
FMaterialInstance* getMaterialInstance(FEngine& engine) const noexcept;
|
||||
FMaterial* getMaterial(FEngine& engine,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept;
|
||||
|
||||
std::pair<backend::PipelineState, backend::Viewport> getPipelineState(FEngine& engine,
|
||||
Variant::type_t variantKey = 0u) const noexcept;
|
||||
// Helper to get a MaterialInstance from a FMaterial
|
||||
// This currently just call FMaterial::getDefaultInstance().
|
||||
static FMaterialInstance* getMaterialInstance(FMaterial const* ma) noexcept;
|
||||
|
||||
// Helper to get a MaterialInstance from a PostProcessMaterial.
|
||||
static FMaterialInstance* getMaterialInstance(FEngine& engine,
|
||||
PostProcessMaterial const& material,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) noexcept;
|
||||
|
||||
private:
|
||||
void loadMaterial(FEngine& engine) const noexcept;
|
||||
@@ -346,39 +353,46 @@ public:
|
||||
mutable FMaterial* mMaterial;
|
||||
uint8_t const* mData;
|
||||
};
|
||||
uint32_t mSize{};
|
||||
mutable bool mHasMaterial{};
|
||||
utils::FixedCapacityVector<ConstantInfo> mConstants{};
|
||||
// mSize == 0 if mMaterial is valid, otherwise mSize > 0
|
||||
mutable uint32_t mSize{};
|
||||
// the objects' must outlive the Slice<>
|
||||
utils::Slice<StaticMaterialInfo::ConstantInfo> mConstants{};
|
||||
};
|
||||
|
||||
void registerPostProcessMaterial(std::string_view name, MaterialInfo const& info);
|
||||
void registerPostProcessMaterial(std::string_view name, StaticMaterialInfo const& info);
|
||||
|
||||
PostProcessMaterial& getPostProcessMaterial(std::string_view name) noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material, uint8_t variant,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void render(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline, backend::Viewport const& scissor,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void render(FrameGraphResources::RenderPassInfo const& out,
|
||||
std::pair<backend::PipelineState, backend::Viewport> const& combo,
|
||||
backend::DriverApi& driver) const noexcept {
|
||||
render(out, combo.first, combo.second, driver);
|
||||
}
|
||||
|
||||
void setFrameUniforms(backend::DriverApi& driver,
|
||||
TypedUniformBuffer<PerViewUib>& uniforms) noexcept;
|
||||
|
||||
void bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept;
|
||||
|
||||
backend::PipelineState getPipelineState(
|
||||
FMaterial const* ma,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept;
|
||||
|
||||
void renderFullScreenQuad(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void renderFullScreenQuadWithScissor(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline,
|
||||
backend::Viewport scissor,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
// Helper for a common case. Don't use in a loop because retrieving the PipelineState
|
||||
// from FMaterialInstance is not trivial.
|
||||
void commitAndRenderFullScreenQuad(backend::DriverApi& driver,
|
||||
FrameGraphResources::RenderPassInfo const& out,
|
||||
FMaterialInstance const* mi,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept;
|
||||
|
||||
private:
|
||||
backend::RenderPrimitiveHandle mFullScreenQuadRph;
|
||||
backend::VertexBufferInfoHandle mFullScreenQuadVbih;
|
||||
backend::DescriptorSetLayoutHandle mPerRenderableDslh;
|
||||
|
||||
FEngine& mEngine;
|
||||
|
||||
mutable SsrPassDescriptorSet mSsrPassDescriptorSet;
|
||||
|
||||
+58
-57
@@ -68,7 +68,6 @@ namespace filament {
|
||||
using namespace backend;
|
||||
|
||||
RenderPassBuilder& RenderPassBuilder::customCommand(
|
||||
FEngine& engine,
|
||||
uint8_t channel,
|
||||
RenderPass::Pass pass,
|
||||
RenderPass::CustomCommand custom,
|
||||
@@ -76,17 +75,17 @@ RenderPassBuilder& RenderPassBuilder::customCommand(
|
||||
RenderPass::Executor::CustomCommandFn const& command) {
|
||||
if (!mCustomCommands.has_value()) {
|
||||
// construct the vector the first time
|
||||
mCustomCommands.emplace(engine.getPerRenderPassArena());
|
||||
mCustomCommands.emplace();
|
||||
}
|
||||
mCustomCommands->emplace_back(channel, pass, custom, order, command);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderPass RenderPassBuilder::build(FEngine& engine) {
|
||||
RenderPass RenderPassBuilder::build(FEngine const& engine, backend::DriverApi& driver) const {
|
||||
assert_invariant(mRenderableSoa);
|
||||
assert_invariant(mScissorViewport.width <= std::numeric_limits<int32_t>::max());
|
||||
assert_invariant(mScissorViewport.height <= std::numeric_limits<int32_t>::max());
|
||||
return RenderPass{ engine, *this };
|
||||
return RenderPass{ engine, driver, *this };
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -107,11 +106,11 @@ void RenderPass::DescriptorSetHandleDeleter::operator()(
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexcept
|
||||
RenderPass::RenderPass(FEngine const& engine, backend::DriverApi& driver,
|
||||
RenderPassBuilder const& builder) noexcept
|
||||
: mRenderableSoa(*builder.mRenderableSoa),
|
||||
mColorPassDescriptorSet(builder.mColorPassDescriptorSet),
|
||||
mScissorViewport(builder.mScissorViewport),
|
||||
mCustomCommands(engine.getPerRenderPassArena()) {
|
||||
mScissorViewport(builder.mScissorViewport) {
|
||||
|
||||
// compute the number of commands we need
|
||||
updateSummedPrimitiveCounts(
|
||||
@@ -127,10 +126,12 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
uint32_t const customCommandCount =
|
||||
builder.mCustomCommands.has_value() ? builder.mCustomCommands->size() : 0;
|
||||
|
||||
// FIXME: builder.mArena must be thread safe eventually
|
||||
Command* const commandBegin = builder.mArena.alloc<Command>(commandCount + customCommandCount);
|
||||
Command* commandEnd = commandBegin + (commandCount + customCommandCount);
|
||||
assert_invariant(commandBegin);
|
||||
|
||||
// FIXME: builder.mArena must be thread safe eventually
|
||||
if (UTILS_UNLIKELY(builder.mArena.getAllocator().isHeapAllocation(commandBegin))) {
|
||||
static bool sLogOnce = true;
|
||||
if (UTILS_UNLIKELY(sLogOnce)) {
|
||||
@@ -150,6 +151,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
builder.mCameraForwardVector);
|
||||
|
||||
if (builder.mCustomCommands.has_value()) {
|
||||
mCustomCommands.reserve(customCommandCount);
|
||||
Command* p = commandBegin + commandCount;
|
||||
for (auto const& [channel, passId, command, order, fn]: builder.mCustomCommands.value()) {
|
||||
appendCustomCommand(p++, channel, passId, command, order, fn);
|
||||
@@ -166,7 +168,9 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
stereoscopicEyeCount *= engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
commandEnd = resize(builder.mArena,
|
||||
instanceify(engine, commandBegin, commandEnd, stereoscopicEyeCount));
|
||||
instanceify(driver,
|
||||
engine.getPerRenderableDescriptorSetLayout().getHandle(),
|
||||
commandBegin, commandEnd, stereoscopicEyeCount));
|
||||
}
|
||||
|
||||
// these are `const` from this point on...
|
||||
@@ -182,7 +186,7 @@ RenderPass::Command* RenderPass::resize(Arena& arena, Command* const last) noexc
|
||||
return last;
|
||||
}
|
||||
|
||||
void RenderPass::appendCommands(FEngine& engine,
|
||||
void RenderPass::appendCommands(FEngine const& engine,
|
||||
Slice<Command> commands,
|
||||
utils::Range<uint32_t> const vr,
|
||||
CommandTypeFlags const commandTypeFlags,
|
||||
@@ -283,17 +287,8 @@ RenderPass::Command* RenderPass::sortCommands(
|
||||
return last;
|
||||
}
|
||||
|
||||
void RenderPass::execute(RenderPass const& pass,
|
||||
FEngine& engine, const char* name,
|
||||
backend::Handle<backend::HwRenderTarget> renderTarget,
|
||||
backend::RenderPassParams params) noexcept {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
driver.beginRenderPass(renderTarget, params);
|
||||
pass.getExecutor().execute(engine, name);
|
||||
driver.endRenderPass();
|
||||
}
|
||||
|
||||
RenderPass::Command* RenderPass::instanceify(FEngine& engine,
|
||||
RenderPass::Command* RenderPass::instanceify(backend::DriverApi& driver,
|
||||
DescriptorSetLayoutHandle perRenderableDescriptorSetLayoutHandle,
|
||||
Command* curr, Command* const last,
|
||||
int32_t eyeCount) const noexcept {
|
||||
SYSTRACE_NAME("instanceify");
|
||||
@@ -352,8 +347,6 @@ RenderPass::Command* RenderPass::instanceify(FEngine& engine,
|
||||
if (UTILS_UNLIKELY(instanceCount > 1)) {
|
||||
drawCallsSavedCount += instanceCount - 1;
|
||||
|
||||
auto& driver = engine.getDriverApi();
|
||||
|
||||
// allocate our staging buffer only if needed
|
||||
if (UTILS_UNLIKELY(!stagingBuffer)) {
|
||||
// Create a temporary UBO for holding the per-renderable data of each primitive,
|
||||
@@ -382,8 +375,7 @@ RenderPass::Command* RenderPass::instanceify(FEngine& engine,
|
||||
// in this case we would need to preserve the default descriptor-set content).
|
||||
// This has the same lifetime as the UBO (see above).
|
||||
mInstancedDescriptorSetHandle = DescriptorSetSharedHandle{
|
||||
driver.createDescriptorSet(
|
||||
engine.getPerRenderableDescriptorSetLayout().getHandle()),
|
||||
driver.createDescriptorSet(perRenderableDescriptorSetLayoutHandle),
|
||||
driver
|
||||
};
|
||||
driver.updateDescriptorSetBuffer(mInstancedDescriptorSetHandle,
|
||||
@@ -418,10 +410,7 @@ RenderPass::Command* RenderPass::instanceify(FEngine& engine,
|
||||
if (UTILS_UNLIKELY(firstSentinel)) {
|
||||
//slog.d << "auto-instancing, saving " << drawCallsSavedCount << " draw calls, out of "
|
||||
// << count << io::endl;
|
||||
|
||||
// we have instanced primitives
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// copy our instanced ubo data
|
||||
driver.updateBufferObjectUnsynchronized(mInstancedUboHandle, {
|
||||
stagingBuffer, sizeof(PerRenderableData) * instancedPrimitiveOffset,
|
||||
@@ -817,13 +806,15 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
const BlendingMode blendingMode = ma->getBlendingMode();
|
||||
const bool translucent = (blendingMode != BlendingMode::OPAQUE
|
||||
&& blendingMode != BlendingMode::MASKED);
|
||||
const bool isPickingVariant = Variant::isPickingVariant(variant);
|
||||
|
||||
cmd.key |= mi->getSortingKey(); // already all set-up for direct or'ing
|
||||
cmd.info.rasterState.culling = mi->getCullingMode();
|
||||
|
||||
// FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()?
|
||||
cmd.info.rasterState.depthWrite = (1 // only keep bit 0
|
||||
& (mi->isDepthWriteEnabled() | (mode == TransparencyMode::TWO_PASSES_ONE_SIDE))
|
||||
& (mi->isDepthWriteEnabled() | (mode == TransparencyMode::TWO_PASSES_ONE_SIDE)
|
||||
| isPickingVariant)
|
||||
& !(filterTranslucentObjects & translucent)
|
||||
& !(depthFilterAlphaMaskedObjects & rs.alphaToCoverage))
|
||||
| writeDepthForShadowCasters;
|
||||
@@ -859,19 +850,13 @@ void RenderPass::Executor::overridePolygonOffset(backend::PolygonOffset const* p
|
||||
}
|
||||
}
|
||||
|
||||
void RenderPass::Executor::overrideScissor(backend::Viewport const* scissor) noexcept {
|
||||
if ((mScissorOverride = (scissor != nullptr))) { // NOLINT(*-assignment-in-if-condition)
|
||||
mScissor = *scissor;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderPass::Executor::overrideScissor(backend::Viewport const& scissor) noexcept {
|
||||
mScissorOverride = true;
|
||||
mScissor = scissor;
|
||||
}
|
||||
|
||||
void RenderPass::Executor::execute(FEngine& engine, const char*) const noexcept {
|
||||
execute(engine, mCommands.begin(), mCommands.end());
|
||||
void RenderPass::Executor::execute(FEngine const& engine, backend::DriverApi& driver) const noexcept {
|
||||
execute(engine, driver, mCommands.begin(), mCommands.end());
|
||||
}
|
||||
|
||||
UTILS_NOINLINE // no need to be inlined
|
||||
@@ -881,15 +866,15 @@ backend::Viewport RenderPass::Executor::applyScissorViewport(
|
||||
// clang vectorizes this!
|
||||
constexpr int32_t maxvali = std::numeric_limits<int32_t>::max();
|
||||
// compute new left/bottom, assume no overflow
|
||||
int32_t l = scissor.left + scissorViewport.left;
|
||||
int32_t b = scissor.bottom + scissorViewport.bottom;
|
||||
int32_t const l = scissor.left + scissorViewport.left;
|
||||
int32_t const b = scissor.bottom + scissorViewport.bottom;
|
||||
// compute right/top without overflowing, scissor.width/height guaranteed
|
||||
// to convert to int32
|
||||
int32_t r = (l > maxvali - int32_t(scissor.width)) ? maxvali : l + int32_t(scissor.width);
|
||||
int32_t t = (b > maxvali - int32_t(scissor.height)) ? maxvali : b + int32_t(scissor.height);
|
||||
// clip to the viewport
|
||||
l = std::max(l, scissorViewport.left);
|
||||
b = std::max(b, scissorViewport.bottom);
|
||||
assert_invariant(l == std::max(l, scissorViewport.left));
|
||||
assert_invariant(b == std::max(b, scissorViewport.bottom));
|
||||
r = std::min(r, scissorViewport.left + int32_t(scissorViewport.width));
|
||||
t = std::min(t, scissorViewport.bottom + int32_t(scissorViewport.height));
|
||||
assert_invariant(r >= l && t >= b);
|
||||
@@ -897,23 +882,26 @@ backend::Viewport RenderPass::Executor::applyScissorViewport(
|
||||
}
|
||||
|
||||
UTILS_NOINLINE // no need to be inlined
|
||||
void RenderPass::Executor::execute(FEngine& engine,
|
||||
const Command* first, const Command* last) const noexcept {
|
||||
void RenderPass::Executor::execute(FEngine const& engine, backend::DriverApi& driver,
|
||||
Command const* first, Command const* last) const noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
SYSTRACE_CONTEXT();
|
||||
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
size_t const capacity = engine.getMinCommandBufferSize();
|
||||
CircularBuffer const& circularBuffer = driver.getCircularBuffer();
|
||||
|
||||
if (first != last) {
|
||||
SYSTRACE_VALUE32("commandCount", last - first);
|
||||
|
||||
bool const scissorOverride = mScissorOverride;
|
||||
if (UTILS_UNLIKELY(scissorOverride)) {
|
||||
// initialize with scissor override
|
||||
// The scissor rectangle is associated to a render pass, so the tracking can be local.
|
||||
backend::Viewport currentScissor{ 0, 0, INT32_MAX, INT32_MAX };
|
||||
bool const hasScissorOverride = mScissorOverride;
|
||||
bool const hasScissorViewport = mHasScissorViewport;
|
||||
if (UTILS_UNLIKELY(hasScissorViewport || hasScissorOverride)) {
|
||||
// we should never have both an override and scissor-viewport
|
||||
assert_invariant(!hasScissorViewport || !hasScissorOverride);
|
||||
currentScissor = mScissor;
|
||||
driver.scissor(mScissor);
|
||||
}
|
||||
|
||||
@@ -964,7 +952,9 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
// check we have enough capacity to write these commandCount commands, if not,
|
||||
// request a new CircularBuffer allocation of `capacity` bytes.
|
||||
if (UTILS_UNLIKELY(circularBuffer.getUsed() > capacity - commandSizeInBytes)) {
|
||||
engine.flush(); // TODO: we should use a "fast" flush if possible
|
||||
// FIXME: eventually we can't flush here because this will be a secondary
|
||||
// command buffer. We will need another solution for overflows.
|
||||
const_cast<FEngine&>(engine).flush();
|
||||
}
|
||||
|
||||
first--;
|
||||
@@ -997,17 +987,24 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
|
||||
if (UTILS_UNLIKELY(mi != info.mi)) {
|
||||
// this is always taken the first time
|
||||
mi = info.mi;
|
||||
assert_invariant(mi);
|
||||
assert_invariant(info.mi);
|
||||
|
||||
mi = info.mi;
|
||||
ma = mi->getMaterial();
|
||||
|
||||
if (UTILS_LIKELY(!scissorOverride)) {
|
||||
// if we have the scissor override, the material instance and scissor-viewport
|
||||
// are ignored (typically used for shadow maps).
|
||||
if (!hasScissorOverride) {
|
||||
// apply the MaterialInstance scissor
|
||||
backend::Viewport scissor = mi->getScissor();
|
||||
if (UTILS_UNLIKELY(mi->hasScissor())) {
|
||||
scissor = applyScissorViewport(mScissorViewport, scissor);
|
||||
if (hasScissorViewport) {
|
||||
// apply the scissor viewport if any
|
||||
scissor = applyScissorViewport(mScissor, scissor);
|
||||
}
|
||||
if (scissor != currentScissor) {
|
||||
currentScissor = scissor;
|
||||
driver.scissor(scissor);
|
||||
}
|
||||
driver.scissor(scissor);
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(!polygonOffsetOverride)) {
|
||||
@@ -1085,7 +1082,9 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
// If the remaining space is less than half the capacity, we flush right away to
|
||||
// allow some headroom for commands that might come later.
|
||||
if (UTILS_UNLIKELY(circularBuffer.getUsed() > capacity / 2)) {
|
||||
engine.flush();
|
||||
// FIXME: eventually we can't flush here because this will be a secondary
|
||||
// command buffer.
|
||||
const_cast<FEngine&>(engine).flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1098,16 +1097,18 @@ RenderPass::Executor::Executor(RenderPass const& pass, Command const* b, Command
|
||||
mInstancedUboHandle(pass.mInstancedUboHandle),
|
||||
mInstancedDescriptorSetHandle(pass.mInstancedDescriptorSetHandle),
|
||||
mColorPassDescriptorSet(pass.mColorPassDescriptorSet),
|
||||
mScissorViewport(pass.mScissorViewport),
|
||||
mScissor(pass.mScissorViewport),
|
||||
mPolygonOffsetOverride(false),
|
||||
mScissorOverride(false) {
|
||||
mHasScissorViewport = mScissor != backend::Viewport{ 0, 0, INT32_MAX, INT32_MAX };
|
||||
assert_invariant(b >= pass.begin());
|
||||
assert_invariant(e <= pass.end());
|
||||
}
|
||||
|
||||
RenderPass::Executor::Executor() noexcept
|
||||
: mPolygonOffsetOverride(false),
|
||||
mScissorOverride(false) {
|
||||
mScissorOverride(false),
|
||||
mHasScissorViewport(false) {
|
||||
}
|
||||
|
||||
RenderPass::Executor::Executor(Executor&& rhs) noexcept = default;
|
||||
|
||||
+28
-28
@@ -32,6 +32,7 @@
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/architecture.h>
|
||||
@@ -309,13 +310,6 @@ public:
|
||||
Command const* end() const noexcept { return mCommandEnd; }
|
||||
bool empty() const noexcept { return begin() == end(); }
|
||||
|
||||
// Helper to execute all the commands generated by this RenderPass
|
||||
static void execute(RenderPass const& pass,
|
||||
FEngine& engine, const char* name,
|
||||
backend::Handle<backend::HwRenderTarget> renderTarget,
|
||||
backend::RenderPassParams params) noexcept;
|
||||
|
||||
|
||||
class BufferObjectHandleDeleter {
|
||||
std::reference_wrapper<backend::DriverApi> driver;
|
||||
public:
|
||||
@@ -350,16 +344,22 @@ public:
|
||||
BufferObjectSharedHandle mInstancedUboHandle;
|
||||
DescriptorSetSharedHandle mInstancedDescriptorSetHandle;
|
||||
ColorPassDescriptorSet const* mColorPassDescriptorSet = nullptr;
|
||||
backend::Viewport mScissorViewport;
|
||||
// this stores either the scissor-viewport or the scissor override
|
||||
backend::Viewport mScissor{ 0, 0, INT32_MAX, INT32_MAX };
|
||||
|
||||
backend::Viewport mScissor{}; // value of scissor override
|
||||
backend::PolygonOffset mPolygonOffset{}; // value of the override
|
||||
bool mPolygonOffsetOverride : 1; // whether to override the polygon offset setting
|
||||
bool mScissorOverride : 1; // whether to override the polygon offset setting
|
||||
// value of the polygon offset override
|
||||
backend::PolygonOffset mPolygonOffset{};
|
||||
// whether to override the polygon offset from the MaterialInstance
|
||||
bool mPolygonOffsetOverride : 1;
|
||||
// whether to override the scissor rectangle from the MaterialInstance
|
||||
bool mScissorOverride : 1;
|
||||
// whether the scissor-viewport is set
|
||||
bool mHasScissorViewport : 1;
|
||||
|
||||
Executor(RenderPass const& pass, Command const* b, Command const* e) noexcept;
|
||||
|
||||
void execute(FEngine& engine, const Command* first, const Command* last) const noexcept;
|
||||
void execute(FEngine const& engine, backend::DriverApi& driver,
|
||||
Command const* first, Command const* last) const noexcept;
|
||||
|
||||
static backend::Viewport applyScissorViewport(
|
||||
backend::Viewport const& scissorViewport,
|
||||
@@ -382,11 +382,9 @@ public:
|
||||
// if non-null, overrides the material's polygon offset
|
||||
void overridePolygonOffset(backend::PolygonOffset const* polygonOffset) noexcept;
|
||||
|
||||
// if non-null, overrides the material's scissor
|
||||
void overrideScissor(backend::Viewport const* scissor) noexcept;
|
||||
void overrideScissor(backend::Viewport const& scissor) noexcept;
|
||||
|
||||
void execute(FEngine& engine, const char* name) const noexcept;
|
||||
void execute(FEngine const& engine, backend::DriverApi& driver) const noexcept;
|
||||
};
|
||||
|
||||
// returns a new executor for this pass
|
||||
@@ -401,11 +399,12 @@ public:
|
||||
private:
|
||||
friend class FRenderer;
|
||||
friend class RenderPassBuilder;
|
||||
RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexcept;
|
||||
RenderPass(FEngine const& engine, backend::DriverApi& driver,
|
||||
RenderPassBuilder const& builder) noexcept;
|
||||
|
||||
// This is the main function of this class, this appends commands to the pass using
|
||||
// the current camera, geometry and flags set. This can be called multiple times if needed.
|
||||
void appendCommands(FEngine& engine,
|
||||
void appendCommands(FEngine const& engine,
|
||||
utils::Slice<Command> commands,
|
||||
utils::Range<uint32_t> visibleRenderables,
|
||||
CommandTypeFlags commandTypeFlags,
|
||||
@@ -420,14 +419,15 @@ private:
|
||||
uint8_t channel, Pass pass, CustomCommand custom, uint32_t order,
|
||||
Executor::CustomCommandFn command);
|
||||
|
||||
static Command* resize(Arena& arena, Command* const last) noexcept;
|
||||
static Command* resize(Arena& arena, Command* last) noexcept;
|
||||
|
||||
// sorts commands then trims sentinels
|
||||
static Command* sortCommands(
|
||||
Command* begin, Command* end) noexcept;
|
||||
|
||||
// instanceify commands then trims sentinels
|
||||
RenderPass::Command* instanceify(FEngine& engine,
|
||||
RenderPass::Command* instanceify(backend::DriverApi& driver,
|
||||
backend::DescriptorSetLayoutHandle perRenderableDescriptorSetLayoutHandle,
|
||||
Command* begin, Command* end,
|
||||
int32_t eyeCount) const noexcept;
|
||||
|
||||
@@ -461,14 +461,13 @@ private:
|
||||
|
||||
FScene::RenderableSoa const& mRenderableSoa;
|
||||
ColorPassDescriptorSet const* const mColorPassDescriptorSet;
|
||||
backend::Viewport const mScissorViewport;
|
||||
backend::Viewport const mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
|
||||
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
|
||||
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
|
||||
mutable BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives
|
||||
mutable DescriptorSetSharedHandle mInstancedDescriptorSetHandle; // a descriptor-set to hold the ubo
|
||||
// a vector for our custom commands
|
||||
using CustomCommandVector = std::vector<Executor::CustomCommandFn,
|
||||
utils::STLAllocator<Executor::CustomCommandFn, LinearAllocatorArena>>;
|
||||
using CustomCommandVector = utils::FixedCapacityVector<Executor::CustomCommandFn>;
|
||||
mutable CustomCommandVector mCustomCommands;
|
||||
};
|
||||
|
||||
@@ -494,11 +493,10 @@ class RenderPassBuilder {
|
||||
uint32_t,
|
||||
RenderPass::Executor::CustomCommandFn>;
|
||||
|
||||
using CustomCommandContainer = std::vector<CustomCommandRecord,
|
||||
utils::STLAllocator<CustomCommandRecord, LinearAllocatorArena>>;
|
||||
using CustomCommandContainer = std::vector<CustomCommandRecord>;
|
||||
|
||||
// we make this optional because it's not used often, and we don't want to have
|
||||
// to construct it by default.
|
||||
// to construct it by default. For the same reason we use a std::vector<>
|
||||
std::optional<CustomCommandContainer> mCustomCommands;
|
||||
|
||||
public:
|
||||
@@ -509,6 +507,8 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
|
||||
// offset by the viewport's left-top and clipped to the viewport's width/height.
|
||||
RenderPassBuilder& scissorViewport(backend::Viewport viewport) noexcept {
|
||||
mScissorViewport = viewport;
|
||||
return *this;
|
||||
@@ -564,14 +564,14 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderPassBuilder& customCommand(FEngine& engine,
|
||||
RenderPassBuilder& customCommand(
|
||||
uint8_t channel,
|
||||
RenderPass::Pass pass,
|
||||
RenderPass::CustomCommand custom,
|
||||
uint32_t order,
|
||||
const RenderPass::Executor::CustomCommandFn& command);
|
||||
|
||||
RenderPass build(FEngine& engine);
|
||||
RenderPass build(FEngine const& engine, backend::DriverApi& driver) const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ RendererUtils::ColorPassOutput RendererUtils::colorPass(
|
||||
}
|
||||
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
passExecutor.execute(engine, resources.getPassName());
|
||||
passExecutor.execute(engine, driver);
|
||||
driver.endRenderPass();
|
||||
|
||||
// color pass is typically heavy, and we don't have much CPU work left after
|
||||
|
||||
@@ -260,7 +260,7 @@ void ResourceAllocator::gc(bool skippedFrame) noexcept {
|
||||
if ((ageDiff >= MAX_AGE_SKIPPED_FRAME && skippedFrame) ||
|
||||
(ageDiff >= mCacheMaxAge && evictedCount < MAX_EVICTION_COUNT)) {
|
||||
evictedCount++;
|
||||
purge(it);
|
||||
it = purge(it);
|
||||
} else {
|
||||
// build the set of ages present in the cache after eviction
|
||||
ages.set(std::min(size_t(31), ageDiff));
|
||||
@@ -280,7 +280,7 @@ void ResourceAllocator::gc(bool skippedFrame) noexcept {
|
||||
for (auto it = textureCache.begin(); it != textureCache.end();) {
|
||||
const size_t ageDiff = age - it->second.age;
|
||||
if (ageDiff >= maxAge) {
|
||||
purge(it);
|
||||
it = purge(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
@@ -306,12 +306,13 @@ void ResourceAllocator::dump(bool brief) const noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceAllocator::purge(
|
||||
ResourceAllocator::CacheContainer::iterator
|
||||
ResourceAllocator::purge(
|
||||
ResourceAllocator::CacheContainer::iterator const& pos) {
|
||||
//slog.d << "purging " << pos->second.handle.getId() << ", age=" << pos->second.age << io::endl;
|
||||
mBackend.destroyTexture(pos->second.handle);
|
||||
mCacheSize -= pos->second.size;
|
||||
mTextureCache.erase(pos);
|
||||
return mTextureCache.erase(pos);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -215,7 +215,8 @@ private:
|
||||
|
||||
using CacheContainer = AssociativeContainer<TextureKey, TextureCachePayload>;
|
||||
|
||||
void purge(ResourceAllocator::CacheContainer::iterator const& pos);
|
||||
ResourceAllocator::CacheContainer::iterator
|
||||
purge(ResourceAllocator::CacheContainer::iterator const& pos);
|
||||
|
||||
backend::DriverApi& mBackend;
|
||||
std::shared_ptr<ResourceAllocatorDisposer> mDisposer;
|
||||
|
||||
@@ -1361,8 +1361,8 @@ math::float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo)
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void ShadowMap::prepareCamera(Transaction const& transaction,
|
||||
FEngine& engine, const CameraInfo& cameraInfo) noexcept {
|
||||
ShadowMapDescriptorSet::prepareCamera(transaction, engine, cameraInfo);
|
||||
backend::DriverApi& driver, const CameraInfo& cameraInfo) noexcept {
|
||||
ShadowMapDescriptorSet::prepareCamera(transaction, driver, cameraInfo);
|
||||
ShadowMapDescriptorSet::prepareLodBias(transaction, 0.0f);
|
||||
}
|
||||
|
||||
@@ -1372,7 +1372,7 @@ void ShadowMap::prepareViewport(Transaction const& transaction,
|
||||
}
|
||||
|
||||
void ShadowMap::prepareTime(Transaction const& transaction,
|
||||
FEngine& engine, math::float4 const& userTime) noexcept {
|
||||
FEngine const& engine, math::float4 const& userTime) noexcept {
|
||||
ShadowMapDescriptorSet::prepareTime(transaction, engine, userTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -193,11 +193,11 @@ public:
|
||||
using Transaction = ShadowMapDescriptorSet::Transaction;
|
||||
|
||||
static void prepareCamera(Transaction const& transaction,
|
||||
FEngine& engine, const CameraInfo& cameraInfo) noexcept;
|
||||
backend::DriverApi& driver, const CameraInfo& cameraInfo) noexcept;
|
||||
static void prepareViewport(Transaction const& transaction,
|
||||
backend::Viewport const& viewport) noexcept;
|
||||
static void prepareTime(Transaction const& transaction,
|
||||
FEngine& engine, math::float4 const& userTime) noexcept;
|
||||
FEngine const& engine, math::float4 const& userTime) noexcept;
|
||||
static void prepareShadowMapping(Transaction const& transaction,
|
||||
bool highPrecision) noexcept;
|
||||
static ShadowMapDescriptorSet::Transaction open(backend::DriverApi& driver) noexcept;
|
||||
|
||||
@@ -314,9 +314,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
// "read" from one of its resource (only writes), so the FrameGraph culls it.
|
||||
builder.sideEffect();
|
||||
},
|
||||
[this, &engine, &view, vsmShadowOptions,
|
||||
scene, mainCameraInfo, userTime, passBuilder = passBuilder](
|
||||
FrameGraphResources const&, auto const& data, DriverApi& driver) mutable {
|
||||
[=, passBuilder = passBuilder,
|
||||
&engine = const_cast<FEngine /*const*/ &>(engine), // FIXME: we want this const
|
||||
&view = const_cast<FView const&>(view)]
|
||||
(FrameGraphResources const&, auto const& data, DriverApi& driver) mutable {
|
||||
|
||||
// Note: we could almost parallel_for the loop below, the problem currently is
|
||||
// that updatePrimitivesLod() updates temporary global state.
|
||||
@@ -363,7 +364,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
const CameraInfo cameraInfo{ shadowMap.getCamera(), mainCameraInfo };
|
||||
|
||||
auto transaction = ShadowMap::open(driver);
|
||||
ShadowMap::prepareCamera(transaction, engine, cameraInfo);
|
||||
ShadowMap::prepareCamera(transaction, driver, cameraInfo);
|
||||
ShadowMap::prepareViewport(transaction, shadowMap.getViewport());
|
||||
ShadowMap::prepareTime(transaction, engine, userTime);
|
||||
ShadowMap::prepareShadowMapping(transaction,
|
||||
@@ -371,8 +372,8 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
shadowMap.commit(transaction, engine, driver);
|
||||
|
||||
// updatePrimitivesLod must be run before RenderPass::appendCommands.
|
||||
view.updatePrimitivesLod(engine,
|
||||
cameraInfo, scene->getRenderableData(), entry.range);
|
||||
FView::updatePrimitivesLod(scene->getRenderableData(),
|
||||
engine, cameraInfo, entry.range);
|
||||
|
||||
// generate and sort the commands for rendering the shadow map
|
||||
|
||||
@@ -394,7 +395,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
.visibilityMask(entry.visibilityMask)
|
||||
.geometry(scene->getRenderableData(), entry.range)
|
||||
.commandTypeFlags(RenderPass::CommandTypeFlags::SHADOW)
|
||||
.build(engine);
|
||||
.build(engine, driver);
|
||||
|
||||
entry.executor = pass.getExecutor();
|
||||
|
||||
@@ -525,7 +526,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
if (entry.shadowMap->hasVisibleShadows()) {
|
||||
entry.shadowMap->bind(driver);
|
||||
entry.executor.overrideScissor(entry.shadowMap->getScissor());
|
||||
entry.executor.execute(engine, "Shadow Pass");
|
||||
entry.executor.execute(engine, driver);
|
||||
}
|
||||
driver.endRenderPass();
|
||||
});
|
||||
@@ -791,9 +792,10 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowMapManager::cullSpotShadowMap(ShadowMap const& shadowMap, FEngine& engine, FView& view,
|
||||
void ShadowMapManager::cullSpotShadowMap(ShadowMap const& shadowMap,
|
||||
FEngine const& engine, FView const& view,
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
|
||||
FScene::LightSoa& lightData) noexcept {
|
||||
FScene::LightSoa const& lightData) noexcept {
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
const size_t lightIndex = shadowMap.getLightIndex();
|
||||
@@ -881,23 +883,23 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowMapManager::cullPointShadowMap(ShadowMap const& shadowMap, FView& view,
|
||||
void ShadowMapManager::cullPointShadowMap(ShadowMap const& shadowMap, FView const& view,
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
|
||||
FScene::LightSoa& lightData) noexcept {
|
||||
FScene::LightSoa const& lightData) noexcept {
|
||||
|
||||
const uint8_t face = shadowMap.getFace();
|
||||
const size_t lightIndex = shadowMap.getLightIndex();
|
||||
uint8_t const face = shadowMap.getFace();
|
||||
size_t const lightIndex = shadowMap.getLightIndex();
|
||||
|
||||
// compute the frustum for this light
|
||||
// for spotlights, we cull shadow casters first because we already know the frustum,
|
||||
// this will help us find better near/far plane later
|
||||
const auto position = lightData.elementAt<FScene::POSITION_RADIUS>(lightIndex).xyz;
|
||||
const auto radius = lightData.elementAt<FScene::POSITION_RADIUS>(lightIndex).w;
|
||||
auto const position = lightData.elementAt<FScene::POSITION_RADIUS>(lightIndex).xyz;
|
||||
auto const radius = lightData.elementAt<FScene::POSITION_RADIUS>(lightIndex).w;
|
||||
|
||||
// compute shadow map frustum for culling
|
||||
const mat4f Mv = ShadowMap::getPointLightViewMatrix(TextureCubemapFace(face), position);
|
||||
const mat4f Mp = mat4f::perspective(90.0f, 1.0f, 0.01f, radius);
|
||||
const Frustum frustum{ math::highPrecisionMultiply(Mp, Mv) };
|
||||
mat4f const Mv = ShadowMap::getPointLightViewMatrix(TextureCubemapFace(face), position);
|
||||
mat4f const Mp = mat4f::perspective(90.0f, 1.0f, 0.01f, radius);
|
||||
Frustum const frustum{ math::highPrecisionMultiply(Mp, Mv) };
|
||||
|
||||
// Cull shadow casters
|
||||
float3 const* worldAABBCenter = renderableData.data<FScene::WORLD_AABB_CENTER>();
|
||||
|
||||
@@ -165,17 +165,17 @@ private:
|
||||
FScene::LightSoa& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept;
|
||||
|
||||
static void cullSpotShadowMap(ShadowMap const& map,
|
||||
FEngine& engine, FView& view,
|
||||
FEngine const& engine, FView const& view,
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
|
||||
FScene::LightSoa& lightData) noexcept;
|
||||
FScene::LightSoa const& lightData) noexcept;
|
||||
|
||||
void preparePointShadowMap(ShadowMap& map,
|
||||
FEngine& engine, FView& view, CameraInfo const& mainCameraInfo,
|
||||
FScene::LightSoa& lightData) noexcept;
|
||||
|
||||
static void cullPointShadowMap(ShadowMap const& shadowMap, FView& view,
|
||||
static void cullPointShadowMap(ShadowMap const& shadowMap, FView const& view,
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
|
||||
FScene::LightSoa& lightData) noexcept;
|
||||
FScene::LightSoa const& lightData) noexcept;
|
||||
|
||||
static void updateSpotVisibilityMasks(
|
||||
uint8_t visibleLayers,
|
||||
@@ -253,8 +253,8 @@ private:
|
||||
return const_cast<ShadowMapManager*>(this)->getCascadedShadowMap();
|
||||
}
|
||||
|
||||
utils::Slice<ShadowMap> getSpotShadowMaps() noexcept {
|
||||
ShadowMap* const p = &getShadowMap(CONFIG_MAX_SHADOW_CASCADES);
|
||||
utils::Slice<ShadowMap> getSpotShadowMaps() const noexcept {
|
||||
ShadowMap const* const p = &getShadowMap(CONFIG_MAX_SHADOW_CASCADES);
|
||||
return { p, mSpotShadowMapCount };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -185,6 +185,14 @@ bool View::isFrontFaceWindingInverted() const noexcept {
|
||||
return downcast(this)->isFrontFaceWindingInverted();
|
||||
}
|
||||
|
||||
void View::setTransparentPickingEnabled(bool enabled) noexcept {
|
||||
downcast(this)->setTransparentPickingEnabled(enabled);
|
||||
}
|
||||
|
||||
bool View::isTransparentPickingEnabled() const noexcept {
|
||||
return downcast(this)->isTransparentPickingEnabled();
|
||||
}
|
||||
|
||||
void View::setDynamicLightingOptions(float zLightNear, float zLightFar) noexcept {
|
||||
downcast(this)->setDynamicLightingOptions(zLightNear, zLightFar);
|
||||
}
|
||||
|
||||
@@ -807,7 +807,10 @@ void FRenderableManager::setMaterialInstanceAt(Instance instance, uint8_t level,
|
||||
primitives[primitiveIndex].setMaterialInstance(mi);
|
||||
AttributeBitset const required = material->getRequiredAttributes();
|
||||
AttributeBitset const declared = primitives[primitiveIndex].getEnabledAttributes();
|
||||
if (UTILS_UNLIKELY((declared & required) != required)) {
|
||||
// Print the warning only when the handle is available. Otherwise this may end up
|
||||
// emitting many invalid warnings as the `declared` bitset is not populated yet.
|
||||
bool const isPrimitiveInitialized = !!primitives[primitiveIndex].getHwHandle();
|
||||
if (UTILS_UNLIKELY(isPrimitiveInitialized && (declared & required) != required)) {
|
||||
slog.w << "[instance=" << instance.asValue() << ", primitive @ " << primitiveIndex
|
||||
<< "] missing required attributes ("
|
||||
<< required << "), declared=" << declared << io::endl;
|
||||
|
||||
+108
-15
@@ -46,6 +46,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/PrivateImplementation-impl.h>
|
||||
@@ -53,8 +54,17 @@
|
||||
#include <utils/ThreadUtils.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "generated/resources/materials.h"
|
||||
|
||||
@@ -73,6 +83,8 @@ struct Engine::BuilderDetails {
|
||||
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
||||
void* mSharedContext = nullptr;
|
||||
bool mPaused = false;
|
||||
std::unordered_map<std::string_view, bool> mFeatureFlags;
|
||||
|
||||
static Config validateConfig(Config config) noexcept;
|
||||
};
|
||||
|
||||
@@ -104,11 +116,11 @@ Engine* FEngine::create(Engine::Builder const& builder) {
|
||||
DriverConfig const driverConfig{
|
||||
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
|
||||
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
|
||||
.disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile,
|
||||
.disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck,
|
||||
.disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile,
|
||||
.disableHandleUseAfterFreeCheck = instance->features.backend.disable_handle_use_after_free_check,
|
||||
.forceGLES2Context = instance->getConfig().forceGLES2Context,
|
||||
.stereoscopicType = instance->getConfig().stereoscopicType,
|
||||
.assertNativeWindowIsValid = instance->getConfig().assertNativeWindowIsValid,
|
||||
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
|
||||
};
|
||||
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
|
||||
|
||||
@@ -219,7 +231,34 @@ FEngine::FEngine(Engine::Builder const& builder) :
|
||||
mMainThreadId(ThreadUtils::getThreadId()),
|
||||
mConfig(builder->mConfig)
|
||||
{
|
||||
// we're assuming we're on the main thread here.
|
||||
// update a feature flag from Engine::Config if the flag is not specified in the Builder
|
||||
auto const featureFlagsBackwardCompatibility =
|
||||
[this, &builder](std::string_view name, bool value) {
|
||||
if (builder->mFeatureFlags.find(name) == builder->mFeatureFlags.end()) {
|
||||
auto* const p = getFeatureFlagPtr(name, true);
|
||||
if (p) {
|
||||
*p = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// update all the features flags specified in the builder
|
||||
for (auto const& feature : builder->mFeatureFlags) {
|
||||
auto* const p = getFeatureFlagPtr(feature.first, true);
|
||||
if (p) {
|
||||
*p = feature.second;
|
||||
}
|
||||
}
|
||||
|
||||
// update "old" feature flags that were specified in Engine::Config
|
||||
featureFlagsBackwardCompatibility("backend.disable_parallel_shader_compile",
|
||||
mConfig.disableParallelShaderCompile);
|
||||
featureFlagsBackwardCompatibility("backend.disable_handle_use_after_free_check",
|
||||
mConfig.disableHandleUseAfterFreeCheck);
|
||||
featureFlagsBackwardCompatibility("backend.opengl.assert_native_window_is_valid",
|
||||
mConfig.assertNativeWindowIsValid);
|
||||
|
||||
// We're assuming we're on the main thread here.
|
||||
// (it may not be the case)
|
||||
mJobSystem.adopt();
|
||||
|
||||
@@ -490,19 +529,31 @@ void FEngine::shutdown() {
|
||||
mPerViewDescriptorSetLayoutSsrVariant.terminate(mHwDescriptorSetLayoutFactory, driver);
|
||||
mPerRenderableDescriptorSetLayout.terminate(mHwDescriptorSetLayoutFactory, driver);
|
||||
|
||||
driver.destroyRenderPrimitive(mFullScreenTriangleRph);
|
||||
driver.destroyRenderPrimitive(std::move(mFullScreenTriangleRph));
|
||||
|
||||
destroy(mFullScreenTriangleIb);
|
||||
mFullScreenTriangleIb = nullptr;
|
||||
|
||||
destroy(mFullScreenTriangleVb);
|
||||
mFullScreenTriangleVb = nullptr;
|
||||
|
||||
destroy(mDummyMorphTargetBuffer);
|
||||
mDummyMorphTargetBuffer = nullptr;
|
||||
|
||||
destroy(mDefaultIblTexture);
|
||||
mDefaultIblTexture = nullptr;
|
||||
|
||||
destroy(mDefaultIbl);
|
||||
mDefaultIbl = nullptr;
|
||||
|
||||
destroy(mDefaultColorGrading);
|
||||
mDefaultColorGrading = nullptr;
|
||||
|
||||
destroy(mDefaultMaterial);
|
||||
mDefaultMaterial = nullptr;
|
||||
|
||||
destroy(mUnprotectedDummySwapchain);
|
||||
mUnprotectedDummySwapchain = nullptr;
|
||||
|
||||
/*
|
||||
* clean-up after the user -- we call terminate on each "leaked" object and clear each list.
|
||||
@@ -519,6 +570,7 @@ void FEngine::shutdown() {
|
||||
|
||||
// this must be done after Skyboxes and before materials
|
||||
destroy(mSkyboxMaterial);
|
||||
mSkyboxMaterial = nullptr;
|
||||
|
||||
cleanupResourceList(std::move(mBufferObjects));
|
||||
cleanupResourceList(std::move(mIndexBuffers));
|
||||
@@ -535,12 +587,12 @@ void FEngine::shutdown() {
|
||||
|
||||
cleanupResourceListLocked(mFenceListLock, std::move(mFences));
|
||||
|
||||
driver.destroyTexture(mDummyOneTexture);
|
||||
driver.destroyTexture(mDummyOneTextureArray);
|
||||
driver.destroyTexture(mDummyZeroTexture);
|
||||
driver.destroyTexture(mDummyZeroTextureArray);
|
||||
driver.destroyTexture(std::move(mDummyOneTexture));
|
||||
driver.destroyTexture(std::move(mDummyOneTextureArray));
|
||||
driver.destroyTexture(std::move(mDummyZeroTexture));
|
||||
driver.destroyTexture(std::move(mDummyZeroTextureArray));
|
||||
|
||||
driver.destroyRenderTarget(mDefaultRenderTarget);
|
||||
driver.destroyRenderTarget(std::move(mDefaultRenderTarget));
|
||||
|
||||
/*
|
||||
* Shutdown the backend...
|
||||
@@ -702,11 +754,11 @@ int FEngine::loop() {
|
||||
DriverConfig const driverConfig {
|
||||
.handleArenaSize = getRequestedDriverHandleArenaSize(),
|
||||
.metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes,
|
||||
.disableParallelShaderCompile = mConfig.disableParallelShaderCompile,
|
||||
.disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck,
|
||||
.disableParallelShaderCompile = features.backend.disable_parallel_shader_compile,
|
||||
.disableHandleUseAfterFreeCheck = features.backend.disable_handle_use_after_free_check,
|
||||
.forceGLES2Context = mConfig.forceGLES2Context,
|
||||
.stereoscopicType = mConfig.stereoscopicType,
|
||||
.assertNativeWindowIsValid = mConfig.assertNativeWindowIsValid,
|
||||
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
|
||||
};
|
||||
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
|
||||
|
||||
@@ -835,8 +887,9 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
|
||||
return p;
|
||||
}
|
||||
|
||||
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material) noexcept {
|
||||
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material);
|
||||
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
|
||||
const char* name) noexcept {
|
||||
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name);
|
||||
if (UTILS_UNLIKELY(p)) { // should never happen
|
||||
auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
|
||||
pos.first->second.insert(p);
|
||||
@@ -1315,6 +1368,32 @@ void FEngine::unprotected() noexcept {
|
||||
mUnprotectedDummySwapchain->makeCurrent(getDriverApi());
|
||||
}
|
||||
|
||||
bool FEngine::setFeatureFlag(char const* name, bool value) noexcept {
|
||||
auto* const p = getFeatureFlagPtr(name);
|
||||
if (p) {
|
||||
*p = value;
|
||||
}
|
||||
return p != nullptr;
|
||||
}
|
||||
|
||||
std::optional<bool> FEngine::getFeatureFlag(char const* name) const noexcept {
|
||||
auto* const p = getFeatureFlagPtr(name, true);
|
||||
if (p) {
|
||||
return *p;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool* FEngine::getFeatureFlagPtr(std::string_view name, bool allowConstant) const noexcept {
|
||||
auto pos = std::find_if(mFeatures.begin(), mFeatures.end(),
|
||||
[name](FeatureFlag const& entry) {
|
||||
return name == entry.name;
|
||||
});
|
||||
|
||||
return (pos != mFeatures.end() && (!pos->constant || allowConstant)) ?
|
||||
const_cast<bool*>(pos->value) : nullptr;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Engine::Builder::Builder() noexcept = default;
|
||||
@@ -1354,6 +1433,20 @@ Engine::Builder& Engine::Builder::paused(bool paused) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Engine::Builder& Engine::Builder::feature(char const* name, bool value) noexcept {
|
||||
mImpl->mFeatureFlags[name] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Engine::Builder& Engine::Builder::features(std::initializer_list<char const *> list) noexcept {
|
||||
for (auto name : list) {
|
||||
if (name) {
|
||||
feature(name, true);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if UTILS_HAS_THREADING
|
||||
|
||||
void Engine::Builder::build(Invocable<void(void*)>&& callback) const {
|
||||
|
||||
@@ -56,21 +56,24 @@
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/IndirectLight.h>
|
||||
#include <filament/Material.h>
|
||||
#include <filament/MaterialEnums.h>
|
||||
#include <filament/Skybox.h>
|
||||
#include <filament/Stream.h>
|
||||
#include <filament/Texture.h>
|
||||
#include <filament/VertexBuffer.h>
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CountDownLatch.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
@@ -208,10 +211,18 @@ public:
|
||||
return mRenderableManager;
|
||||
}
|
||||
|
||||
FRenderableManager const& getRenderableManager() const noexcept {
|
||||
return mRenderableManager;
|
||||
}
|
||||
|
||||
FLightManager& getLightManager() noexcept {
|
||||
return mLightManager;
|
||||
}
|
||||
|
||||
FLightManager const& getLightManager() const noexcept {
|
||||
return mLightManager;
|
||||
}
|
||||
|
||||
FCameraManager& getCameraManager() noexcept {
|
||||
return mCameraManager;
|
||||
}
|
||||
@@ -303,7 +314,8 @@ public:
|
||||
FMaterialInstance* createMaterialInstance(const FMaterial* material,
|
||||
const FMaterialInstance* other, const char* name) noexcept;
|
||||
|
||||
FMaterialInstance* createMaterialInstance(const FMaterial* material) noexcept;
|
||||
FMaterialInstance* createMaterialInstance(const FMaterial* material,
|
||||
const char* name) noexcept;
|
||||
|
||||
FScene* createScene() noexcept;
|
||||
FView* createView() noexcept;
|
||||
@@ -422,8 +434,11 @@ public:
|
||||
|
||||
bool execute();
|
||||
|
||||
utils::JobSystem& getJobSystem() noexcept {
|
||||
return mJobSystem;
|
||||
utils::JobSystem& getJobSystem() const noexcept {
|
||||
// JobSystem is thread-safe, and it's always okay to return a non-const one,
|
||||
// it's conceptually the same as if we were holding a non-const reference, as opposed
|
||||
// to by-value class attribute.
|
||||
return const_cast<utils::JobSystem&>(mJobSystem);
|
||||
}
|
||||
|
||||
std::default_random_engine& getRandomEngine() {
|
||||
@@ -660,6 +675,36 @@ public:
|
||||
} stereo;
|
||||
matdbg::DebugServer* server = nullptr;
|
||||
} debug;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
bool assert_native_window_is_valid = false;
|
||||
} opengl;
|
||||
bool disable_parallel_shader_compile = false;
|
||||
bool disable_handle_use_after_free_check = false;
|
||||
} backend;
|
||||
} features;
|
||||
|
||||
std::array<Engine::FeatureFlag, sizeof(features)> const mFeatures{{
|
||||
{ "backend.disable_parallel_shader_compile",
|
||||
"Disable parallel shader compilation in GL and Metal backends.",
|
||||
&features.backend.disable_parallel_shader_compile, true },
|
||||
{ "backend.disable_handle_use_after_free_check",
|
||||
"Disable Handle<> use-after-free checks.",
|
||||
&features.backend.disable_handle_use_after_free_check, true },
|
||||
{ "backend.opengl.assert_native_window_is_valid",
|
||||
"Asserts that the ANativeWindow is valid when rendering starts.",
|
||||
&features.backend.opengl.assert_native_window_is_valid, true }
|
||||
}};
|
||||
|
||||
utils::Slice<const Engine::FeatureFlag> getFeatureFlags() const noexcept {
|
||||
return { mFeatures.data(), mFeatures.size() };
|
||||
}
|
||||
|
||||
bool setFeatureFlag(char const* name, bool value) noexcept;
|
||||
std::optional<bool> getFeatureFlag(char const* name) const noexcept;
|
||||
bool* getFeatureFlagPtr(std::string_view name, bool allowConstant = false) const noexcept;
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(Engine)
|
||||
|
||||
@@ -55,6 +55,11 @@ IndexBuffer* IndexBuffer::Builder::build(Engine& engine) {
|
||||
|
||||
FIndexBuffer::FIndexBuffer(FEngine& engine, const IndexBuffer::Builder& builder)
|
||||
: mIndexCount(builder->mIndexCount) {
|
||||
auto& name = builder.getName();
|
||||
const char* const tag = name.empty() ? "(no tag)" : name.c_str_safe();
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
builder->mIndexType == IndexType::UINT || builder->mIndexType == IndexType::USHORT)
|
||||
<< "Invalid index type " << static_cast<int>(builder->mIndexType) << ", tag=" << tag;
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
mHandle = driver.createIndexBuffer(
|
||||
(backend::ElementType)builder->mIndexType,
|
||||
|
||||
@@ -321,11 +321,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
|
||||
parser->getSpecularAntiAliasingThreshold(&mSpecularAntiAliasingThreshold);
|
||||
}
|
||||
|
||||
processBlendingMode(parser);
|
||||
processSpecializationConstants(engine, builder, parser);
|
||||
processPushConstants(engine, parser);
|
||||
processDepthVariants(engine, parser);
|
||||
processDescriptorSets(engine, parser);
|
||||
parser->hasCustomDepthShader(&mHasCustomDepthShader);
|
||||
|
||||
mPerViewLayoutIndex = ColorPassDescriptorSet::getIndex(
|
||||
mIsVariantLit,
|
||||
@@ -333,6 +329,12 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
|
||||
mRefractionMode == RefractionMode::SCREEN_SPACE,
|
||||
!(mVariantFilterMask & +UserVariantFilterBit::FOG));
|
||||
|
||||
processBlendingMode(parser);
|
||||
processSpecializationConstants(engine, builder, parser);
|
||||
processPushConstants(engine, parser);
|
||||
processDescriptorSets(engine, parser);
|
||||
precacheDepthVariants(engine);
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
// Register the material with matdbg.
|
||||
matdbg::DebugServer* server = downcast(engine).debug.server;
|
||||
@@ -346,48 +348,21 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
|
||||
FMaterial::~FMaterial() noexcept = default;
|
||||
|
||||
void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept {
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE) {
|
||||
DriverApi& driverApi = mEngine.getDriverApi();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
|
||||
Variant const variant(k);
|
||||
if ((k & variantMask) == variantValue) {
|
||||
if (UTILS_LIKELY(!mIsDefaultMaterial)) {
|
||||
// The depth variants may be shared with the default material, in which case
|
||||
// we should not free it now.
|
||||
bool const isSharedVariant =
|
||||
Variant::isValidDepthVariant(variant) && !mHasCustomDepthShader;
|
||||
if (isSharedVariant) {
|
||||
// we don't own this variant, skip.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
driverApi.destroyProgram(cachedPrograms[k]);
|
||||
cachedPrograms[k].clear();
|
||||
}
|
||||
// Note: This API is not public at the moment, so it's okay to have some debugging logs
|
||||
// and extra checks.
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE &&
|
||||
!mIsDefaultMaterial &&
|
||||
!mHasCustomDepthShader) {
|
||||
// it would be unsafe to invalidate any of the cached depth variant
|
||||
if (UTILS_UNLIKELY(!((variantMask & Variant::DEP) && !(variantValue & Variant::DEP)))) {
|
||||
slog.w << io::hex << "FMaterial::invalidate("
|
||||
<< +variantMask << ", " << +variantValue
|
||||
<< ") would corrupt the depth variant cache" << io::endl;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
|
||||
FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial();
|
||||
for (Variant const variant: pDefaultMaterial->mDepthVariants) {
|
||||
pDefaultMaterial->prepareProgram(variant);
|
||||
if (!cachedPrograms[variant.key]) {
|
||||
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (mMaterialDomain == MaterialDomain::POST_PROCESS) {
|
||||
DriverApi& driverApi = mEngine.getDriverApi();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; k < n; ++k) {
|
||||
if ((k & variantMask) == variantValue) {
|
||||
driverApi.destroyProgram(cachedPrograms[k]);
|
||||
cachedPrograms[k].clear();
|
||||
}
|
||||
}
|
||||
} else if (mMaterialDomain == MaterialDomain::COMPUTE) {
|
||||
// TODO: handle compute variants if any
|
||||
variantMask |= Variant::DEP;
|
||||
variantValue &= ~Variant::DEP;
|
||||
}
|
||||
destroyPrograms(mEngine, variantMask, variantValue);
|
||||
}
|
||||
|
||||
void FMaterial::terminate(FEngine& engine) {
|
||||
@@ -462,13 +437,13 @@ 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);
|
||||
return mEngine.createMaterialInstance(this, name);
|
||||
}
|
||||
}
|
||||
|
||||
FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
|
||||
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
|
||||
mDefaultMaterialInstance = mEngine.createMaterialInstance(this);
|
||||
mDefaultMaterialInstance = mEngine.createMaterialInstance(this, mName.c_str());
|
||||
mDefaultMaterialInstance->setDefaultInstance(true);
|
||||
}
|
||||
return mDefaultMaterialInstance;
|
||||
@@ -627,10 +602,41 @@ Program FMaterial::getProgramWithVariants(
|
||||
}
|
||||
|
||||
void FMaterial::createAndCacheProgram(Program&& p, Variant variant) const noexcept {
|
||||
auto program = mEngine.getDriverApi().createProgram(std::move(p));
|
||||
mEngine.getDriverApi().setDebugTag(program.getId(), mName);
|
||||
FEngine const& engine = mEngine;
|
||||
DriverApi& driverApi = mEngine.getDriverApi();
|
||||
|
||||
// Check if the default material has this program cached
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE &&
|
||||
!mIsDefaultMaterial && !mHasCustomDepthShader &&
|
||||
Variant::isValidDepthVariant(variant)) {
|
||||
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
|
||||
if (pDefaultMaterial) {
|
||||
auto program = pDefaultMaterial->mCachedPrograms[variant.key];
|
||||
if (program) {
|
||||
mCachedPrograms[variant.key] = program;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto program = driverApi.createProgram(std::move(p));
|
||||
driverApi.setDebugTag(program.getId(), mName);
|
||||
assert_invariant(program);
|
||||
mCachedPrograms[variant.key] = program;
|
||||
|
||||
// If the default material doesn't already have this program cached, and all caching conditions
|
||||
// are met (Surface Domain and no custom depth shader), cache it now.
|
||||
// New Materials will inherit these program automatically.
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE &&
|
||||
!mIsDefaultMaterial && !mHasCustomDepthShader &&
|
||||
Variant::isValidDepthVariant(variant)) {
|
||||
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
|
||||
if (pDefaultMaterial && !pDefaultMaterial->mCachedPrograms[variant.key]) {
|
||||
// set the tag to the default material name
|
||||
driverApi.setDebugTag(program.getId(), mName);
|
||||
pDefaultMaterial->mCachedPrograms[variant.key] = program;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const noexcept {
|
||||
@@ -735,22 +741,76 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pVariants) {
|
||||
#endif // FILAMENT_ENABLE_MATDBG
|
||||
|
||||
|
||||
void FMaterial::destroyPrograms(FEngine& engine) {
|
||||
void FMaterial::destroyPrograms(FEngine& engine,
|
||||
Variant::type_t const variantMask, Variant::type_t const variantValue) {
|
||||
|
||||
DriverApi& driverApi = engine.getDriverApi();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
|
||||
const Variant variant(k);
|
||||
if (!mIsDefaultMaterial) {
|
||||
// The depth variants may be shared with the default material, in which case
|
||||
// we should not free it now.
|
||||
bool const isSharedVariant = Variant::isValidDepthVariant(variant) && !mHasCustomDepthShader;
|
||||
if (isSharedVariant) {
|
||||
// we don't own this variant, skip.
|
||||
continue;
|
||||
|
||||
switch (mMaterialDomain) {
|
||||
case MaterialDomain::SURFACE: {
|
||||
if (mIsDefaultMaterial || mHasCustomDepthShader) {
|
||||
// default material or we have custom depth shaders, we destroy all variants
|
||||
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
|
||||
if ((k & variantMask) == variantValue) {
|
||||
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
|
||||
// of variants, and this generates traffic in the command queue.
|
||||
if (cachedPrograms[k]) {
|
||||
driverApi.destroyProgram(std::move(cachedPrograms[k]));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The depth variants may be shared with the default material, in which case
|
||||
// we should not free them now.
|
||||
|
||||
// During Engine::shutdown(), auto-cleanup destroys the default material first,
|
||||
// so this can be null, but this is only used for debugging.
|
||||
UTILS_UNUSED_IN_RELEASE
|
||||
auto UTILS_NULLABLE pDefaultMaterial = engine.getDefaultMaterial();
|
||||
|
||||
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
|
||||
if ((k & variantMask) == variantValue) {
|
||||
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
|
||||
// of variant, and this generates traffic in the command queue.
|
||||
if (cachedPrograms[k]) {
|
||||
if (Variant::isValidDepthVariant(Variant(k))) {
|
||||
// By construction this should always be true, because this
|
||||
// field is populated only when a material creates the program
|
||||
// for this variant.
|
||||
// During Engine::shutdown, auto-cleanup destroys the
|
||||
// default material first
|
||||
assert_invariant(!pDefaultMaterial ||
|
||||
pDefaultMaterial->mCachedPrograms[k]);
|
||||
// we don't own this variant, skip, but clear the entry.
|
||||
cachedPrograms[k].clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
driverApi.destroyProgram(std::move(cachedPrograms[k]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MaterialDomain::POST_PROCESS: {
|
||||
for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; k < n; ++k) {
|
||||
if ((k & variantMask) == variantValue) {
|
||||
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
|
||||
// of variant, and this generates traffic in the command queue.
|
||||
if (cachedPrograms[k]) {
|
||||
driverApi.destroyProgram(std::move(cachedPrograms[k]));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MaterialDomain::COMPUTE: {
|
||||
// Compute programs don't have variants
|
||||
driverApi.destroyProgram(std::move(cachedPrograms[0]));
|
||||
break;
|
||||
}
|
||||
driverApi.destroyProgram(cachedPrograms[k]);
|
||||
cachedPrograms[k].clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,34 +1075,17 @@ void FMaterial::processPushConstants(FEngine& engine, MaterialParser const* pars
|
||||
});
|
||||
}
|
||||
|
||||
void FMaterial::processDepthVariants(FEngine& engine, MaterialParser const* const parser) {
|
||||
parser->hasCustomDepthShader(&mHasCustomDepthShader);
|
||||
|
||||
if (UTILS_UNLIKELY(mIsDefaultMaterial)) {
|
||||
assert_invariant(mMaterialDomain == MaterialDomain::SURFACE);
|
||||
filaflat::MaterialChunk const& materialChunk{ parser->getMaterialChunk() };
|
||||
auto variants = FixedCapacityVector<Variant>::with_capacity(materialChunk.getShaderCount());
|
||||
materialChunk.visitShaders([&variants](
|
||||
ShaderModel, Variant variant, ShaderStage) {
|
||||
if (Variant::isValidDepthVariant(variant)) {
|
||||
variants.push_back(variant);
|
||||
}
|
||||
});
|
||||
std::sort(variants.begin(), variants.end(),
|
||||
[](Variant lhs, Variant rhs) { return lhs.key < rhs.key; });
|
||||
auto pos = std::unique(variants.begin(), variants.end());
|
||||
variants.resize(std::distance(variants.begin(), pos));
|
||||
std::swap(mDepthVariants, variants);
|
||||
}
|
||||
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE) {
|
||||
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
|
||||
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (Variant const variant: pDefaultMaterial->mDepthVariants) {
|
||||
pDefaultMaterial->prepareProgram(variant);
|
||||
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
|
||||
}
|
||||
void FMaterial::precacheDepthVariants(FEngine& engine) {
|
||||
// if possible pre-cache all depth variants from the default material
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE &&
|
||||
!mIsDefaultMaterial &&
|
||||
!mHasCustomDepthShader) {
|
||||
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
|
||||
assert_invariant(pDefaultMaterial);
|
||||
auto const allDepthVariants = VariantUtils::getDepthVariants();
|
||||
for (auto const variant: allDepthVariants) {
|
||||
assert_invariant(Variant::isValidDepthVariant(variant));
|
||||
mCachedPrograms[variant.key] = pDefaultMaterial->mCachedPrograms[variant.key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,9 @@ public:
|
||||
|
||||
uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; }
|
||||
|
||||
void destroyPrograms(FEngine& engine);
|
||||
void destroyPrograms(FEngine& engine,
|
||||
Variant::type_t variantMask = 0,
|
||||
Variant::type_t variantValue = 0);
|
||||
|
||||
// return the id of a specialization constant specified by name for this material
|
||||
std::optional<uint32_t> getSpecializationConstantId(std::string_view name) const noexcept ;
|
||||
@@ -282,7 +284,7 @@ private:
|
||||
|
||||
void processPushConstants(FEngine& engine, MaterialParser const* parser);
|
||||
|
||||
void processDepthVariants(FEngine& engine, MaterialParser const* parser);
|
||||
void precacheDepthVariants(FEngine& engine);
|
||||
|
||||
void processDescriptorSets(FEngine& engine, MaterialParser const* parser);
|
||||
|
||||
@@ -331,7 +333,6 @@ private:
|
||||
SamplerInterfaceBlock mSamplerInterfaceBlock;
|
||||
BufferInterfaceBlock mUniformInterfaceBlock;
|
||||
SubpassInfo mSubpassInfo;
|
||||
utils::FixedCapacityVector<Variant> mDepthVariants; // only populated with default material
|
||||
|
||||
using BindingUniformInfoContainer = utils::FixedCapacityVector<std::tuple<
|
||||
uint8_t, utils::CString, backend::Program::UniformInfo>>;
|
||||
|
||||
@@ -56,7 +56,8 @@ namespace filament {
|
||||
|
||||
using namespace backend;
|
||||
|
||||
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept
|
||||
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
const char* name) noexcept
|
||||
: mMaterial(material),
|
||||
mDescriptorSet(material->getDescriptorSetLayout()),
|
||||
mCulling(CullingMode::BACK),
|
||||
@@ -66,7 +67,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material)
|
||||
mHasScissor(false),
|
||||
mIsDoubleSided(false),
|
||||
mIsDefaultInstance(false),
|
||||
mTransparencyMode(TransparencyMode::DEFAULT) {
|
||||
mTransparencyMode(TransparencyMode::DEFAULT),
|
||||
mName(name ? CString(name) : material->getName()) {
|
||||
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ class FTexture;
|
||||
|
||||
class FMaterialInstance : public MaterialInstance {
|
||||
public:
|
||||
FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept;
|
||||
FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
const char* name) noexcept;
|
||||
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
|
||||
FMaterialInstance(const FMaterialInstance& rhs) = delete;
|
||||
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
|
||||
|
||||
@@ -407,6 +407,9 @@ void FRenderer::endFrame() {
|
||||
driver.debugThreading();
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(engine.isValid(mSwapChain))
|
||||
<< "SwapChain must remain valid until endFrame is called.";
|
||||
|
||||
if (mSwapChain) {
|
||||
mSwapChain->commit(driver);
|
||||
mSwapChain = nullptr;
|
||||
@@ -581,6 +584,10 @@ void FRenderer::render(FView const* view) {
|
||||
void FRenderer::renderInternal(FView const* view) {
|
||||
FEngine& engine = mEngine;
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!view->hasPostProcessPass() ||
|
||||
engine.hasFeatureLevel(FeatureLevel::FEATURE_LEVEL_1))
|
||||
<< "post-processing is not supported at FEATURE_LEVEL_0";
|
||||
|
||||
// per-renderpass data
|
||||
RootArenaScope rootArenaScope(engine.getPerRenderPassArena());
|
||||
|
||||
@@ -918,8 +925,8 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
|
||||
// updatePrimitivesLod must be run before appendCommands and once for each set
|
||||
// of RenderPass::setCamera / RenderPass::setGeometry calls.
|
||||
view.updatePrimitivesLod(engine, cameraInfo,
|
||||
scene.getRenderableData(), view.getVisibleRenderables());
|
||||
FView::updatePrimitivesLod(scene.getRenderableData(),
|
||||
engine, cameraInfo, view.getVisibleRenderables());
|
||||
|
||||
passBuilder.camera(cameraInfo);
|
||||
passBuilder.geometry(scene.getRenderableData(), view.getVisibleRenderables());
|
||||
@@ -962,17 +969,74 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// structure pass -- automatically culled if not used
|
||||
// Currently it consists of a simple depth pass.
|
||||
// This is normally used by SSAO and contact-shadows
|
||||
// This is normally used by SSAO and contact-shadows.
|
||||
// Also, picking is handled here if transparent picking is disabled.
|
||||
|
||||
// TODO: the scaling should depends on all passes that need the structure pass
|
||||
const auto [structure, picking_] = ppm.structure(fg,
|
||||
passBuilder, renderFlags, svp.width, svp.height, {
|
||||
.scale = aoOptions.resolution,
|
||||
.picking = view.hasPicking()
|
||||
.picking = view.hasPicking() && !view.isTransparentPickingEnabled()
|
||||
});
|
||||
const auto picking = picking_;
|
||||
auto picking = picking_;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Picking pass -- automatically culled if not used
|
||||
// Picking is handled here if transparent picking is enabled.
|
||||
|
||||
if (view.hasPicking()) {
|
||||
if (view.isTransparentPickingEnabled()) {
|
||||
struct PickingRenderPassData {
|
||||
FrameGraphId<FrameGraphTexture> depth;
|
||||
FrameGraphId<FrameGraphTexture> picking;
|
||||
};
|
||||
auto& pickingRenderPass = fg.addPass<PickingRenderPassData>("Picking Render Pass",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
bool const isFL0 = mEngine.getDriverApi().getFeatureLevel() ==
|
||||
FeatureLevel::FEATURE_LEVEL_0;
|
||||
|
||||
// TODO: Specify the precision for picking pass
|
||||
uint32_t const width = std::max(32u,
|
||||
(uint32_t)std::ceil(float(svp.width) * aoOptions.resolution));
|
||||
uint32_t const height = std::max(32u,
|
||||
(uint32_t)std::ceil(float(svp.height) * aoOptions.resolution));
|
||||
data.depth = builder.createTexture("Depth Buffer", {
|
||||
.width = width, .height = height,
|
||||
.format = isFL0 ? TextureFormat::DEPTH24 : TextureFormat::DEPTH32F });
|
||||
|
||||
data.depth = builder.write(data.depth,
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
|
||||
data.picking = builder.createTexture("Picking Buffer", {
|
||||
.width = width, .height = height,
|
||||
.format = isFL0 ? TextureFormat::RGBA8 : TextureFormat::RG32F });
|
||||
|
||||
data.picking = builder.write(data.picking,
|
||||
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
|
||||
builder.declareRenderPass("Picking Render Target", {
|
||||
.attachments = {.color = { data.picking }, .depth = data.depth },
|
||||
.clearFlags = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH
|
||||
});
|
||||
},
|
||||
[=, passBuilder = passBuilder](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) mutable {
|
||||
Variant pickingVariant(Variant::DEPTH_VARIANT);
|
||||
pickingVariant.setPicking(true);
|
||||
|
||||
auto out = resources.getRenderPassInfo();
|
||||
passBuilder.renderFlags(renderFlags);
|
||||
passBuilder.variant(pickingVariant);
|
||||
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::DEPTH);
|
||||
|
||||
RenderPass const pass{ passBuilder.build(mEngine, driver) };
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
pass.getExecutor().execute(mEngine, driver);
|
||||
driver.endRenderPass();
|
||||
});
|
||||
picking = pickingRenderPass->picking;
|
||||
}
|
||||
|
||||
struct PickingResolvePassData {
|
||||
FrameGraphId<FrameGraphTexture> picking;
|
||||
};
|
||||
@@ -1053,7 +1117,11 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
|
||||
// know at this point. This will usually be the case when post-process is disabled.
|
||||
// FIXME: we probably should take the dynamic scaling into account too
|
||||
passBuilder.scissorViewport(hasPostProcess ? xvp : vp);
|
||||
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
|
||||
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
|
||||
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
|
||||
(isRenderingMultiview && engine.debug.stereo.combine_multiview_images);
|
||||
passBuilder.scissorViewport(useIntermediateBuffer ? xvp : vp);
|
||||
|
||||
// This one doesn't need to be a FrameGraph pass because it always happens by construction
|
||||
// (i.e. it won't be culled, unless everything is culled), so no need to complexify things.
|
||||
@@ -1068,7 +1136,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
|
||||
if (colorGradingConfigForColor.asSubpass) {
|
||||
// append color grading subpass after all other passes
|
||||
passBuilder.customCommand(engine, 3,
|
||||
passBuilder.customCommand(3,
|
||||
RenderPass::Pass::BLENDED,
|
||||
RenderPass::CustomCommand::EPILOG,
|
||||
0, [&ppm, &driver, colorGradingConfigForColor]() {
|
||||
@@ -1076,7 +1144,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
});
|
||||
} else if (colorGradingConfig.customResolve) {
|
||||
// append custom resolve subpass after all other passes
|
||||
passBuilder.customCommand(engine, 3,
|
||||
passBuilder.customCommand(3,
|
||||
RenderPass::Pass::BLENDED,
|
||||
RenderPass::CustomCommand::EPILOG,
|
||||
0, [&ppm, &driver]() {
|
||||
@@ -1094,7 +1162,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
passBuilder.renderFlags(renderFlags);
|
||||
}
|
||||
|
||||
RenderPass const pass{ passBuilder.build(engine) };
|
||||
RenderPass const pass{ passBuilder.build(engine, driver) };
|
||||
|
||||
FrameGraphTexture::Descriptor colorBufferDesc = {
|
||||
.width = config.physicalViewport.width,
|
||||
|
||||
@@ -1065,8 +1065,8 @@ inline void FView::computeLightCameraDistances(
|
||||
}
|
||||
}
|
||||
|
||||
void FView::updatePrimitivesLod(FEngine& engine, const CameraInfo&,
|
||||
FScene::RenderableSoa& renderableData, Range visible) noexcept {
|
||||
void FView::updatePrimitivesLod(FScene::RenderableSoa& renderableData,
|
||||
FEngine const& engine, CameraInfo const&, Range visible) noexcept {
|
||||
FRenderableManager const& rcm = engine.getRenderableManager();
|
||||
for (uint32_t const index : visible) {
|
||||
uint8_t const level = 0; // TODO: pick the proper level of detail
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user