Compare commits
82
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7a82f7b3 | ||
|
|
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 | ||
|
|
4ee33cd591 | ||
|
|
fd2f9555f1 | ||
|
|
ccdb58e93c | ||
|
|
1eb1df2cf2 | ||
|
|
d784ce311f | ||
|
|
875b295967 | ||
|
|
b1b2d7072d | ||
|
|
014e6bcbde | ||
|
|
614dbb44d5 | ||
|
|
40c0d59464 | ||
|
|
1b0ff3aadb | ||
|
|
164a25cac4 | ||
|
|
2bbcb6de4c | ||
|
|
58aa74b4ae | ||
|
|
26b42a7685 | ||
|
|
99b55cb888 | ||
|
|
4375ffb3e8 | ||
|
|
e9aeb9312b | ||
|
|
4be172050d | ||
|
|
3b9fc8f751 | ||
|
|
3bd4c45d6e | ||
|
|
1809aa7b11 | ||
|
|
739d4007e2 | ||
|
|
3ed9e8f2c0 | ||
|
|
e77ae6ec2c | ||
|
|
4f5369cefa | ||
|
|
93d28bc16a | ||
|
|
e85c22c6f5 | ||
|
|
2cade209b5 | ||
|
|
3d4fc7852b | ||
|
|
7dc17980a3 | ||
|
|
7da09a7f45 | ||
|
|
b97221b8de | ||
|
|
3f37efe4c9 | ||
|
|
9d57ced452 | ||
|
|
fe15a3047e | ||
|
|
67f37d4c15 | ||
|
|
c5fe185834 | ||
|
|
4c5261106a | ||
|
|
2147a7a640 | ||
|
|
dd7106bfcf | ||
|
|
b26c5ef0dc | ||
|
|
d16fc01cfe | ||
|
|
0fd2436cd1 | ||
|
|
374fc6b519 | ||
|
|
d4bb6b82df | ||
|
|
351d77f87b | ||
|
|
72ba2eee6d | ||
|
|
5974065798 | ||
|
|
e9e7abe287 |
@@ -0,0 +1,9 @@
|
||||
name: 'ubuntu apt add deb-src'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: "ubuntu apt add deb-src"
|
||||
run: |
|
||||
echo "deb-src http://archive.ubuntu.com/ubuntu jammy main restricted universe" | sudo tee /etc/apt/sources.list.d/my.list
|
||||
sudo apt-get update
|
||||
shell: bash
|
||||
@@ -8,35 +8,15 @@ on:
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
build-android-armv7:
|
||||
name: build-android-armv7
|
||||
runs-on: macos-14
|
||||
build-android:
|
||||
name: build-android
|
||||
# We intentially use a larger runner here to enable larger disk space
|
||||
# (standard linux runner will fail on disk space and faster build time).
|
||||
runs-on: ubuntu-22.04-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
build-abi: armeabi-v7a
|
||||
|
||||
build-android-armv8a:
|
||||
name: build-android-armv8a
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
build-abi: arm64-v8a
|
||||
|
||||
build-android-x86_64:
|
||||
name: build-android-x86_64
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
build-abi: x86_64
|
||||
build-abi: armeabi-v7a,arm64-v8a,x86_64
|
||||
|
||||
@@ -10,14 +10,14 @@ on:
|
||||
jobs:
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-14
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/ios && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-ios
|
||||
path: out/filament-release-ios.tgz
|
||||
|
||||
@@ -10,14 +10,14 @@ on:
|
||||
jobs:
|
||||
build-linux:
|
||||
name: build-linux
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/linux && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-linux
|
||||
path: out/filament-release-linux.tgz
|
||||
|
||||
@@ -10,14 +10,14 @@ on:
|
||||
jobs:
|
||||
build-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-14
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/mac && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-mac
|
||||
path: out/filament-release-darwin.tgz
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-22.04]
|
||||
os: [macos-14-xlarge, ubuntu-22.04-16core]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2019
|
||||
runs-on: win-2019-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: macos-14
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
runs-on: macos-14
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
@@ -69,10 +69,25 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: macos-14
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/web && printf "y" | ./build.sh presubmit
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
runs-on: ubuntu-22.04-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: ./.github/actions/ubuntu-apt-add-src
|
||||
- name: Run script
|
||||
run: |
|
||||
source ./build/linux/ci-common.sh && bash test/renderdiff_tests.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff_tests
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-22.04]
|
||||
os: [macos-14-xlarge, ubuntu-22.04-32core]
|
||||
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: macos-14
|
||||
runs-on: ubuntu-22.04-16core
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
|
||||
|
||||
steps:
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: macos-14
|
||||
runs-on: ubuntu-22.04-16core
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
||||
|
||||
steps:
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-14
|
||||
runs-on: macos-14-xlarge
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'ios'
|
||||
|
||||
steps:
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2019-32core
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'windows'
|
||||
|
||||
steps:
|
||||
@@ -205,6 +205,7 @@ jobs:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
run: |
|
||||
build\windows\build-github.bat release
|
||||
echo on
|
||||
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
|
||||
shell: cmd
|
||||
- uses: actions/github-script@v6
|
||||
|
||||
@@ -10,14 +10,14 @@ on:
|
||||
jobs:
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: macos-14
|
||||
runs-on: ubuntu-22.04-16core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/web && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-web
|
||||
path: out/filament-release-web.tgz
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2019-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
run: |
|
||||
build\windows\build-github.bat continuous
|
||||
shell: cmd
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-windows
|
||||
path: out/filament-windows.tgz
|
||||
|
||||
@@ -45,6 +45,8 @@ option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
|
||||
|
||||
option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF)
|
||||
|
||||
option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF)
|
||||
|
||||
set(FILAMENT_NDK_VERSION "" CACHE STRING
|
||||
"Android NDK version or version prefix to be used when building for Android."
|
||||
)
|
||||
@@ -73,6 +75,10 @@ set(FILAMENT_BACKEND_DEBUG_FLAG "" CACHE STRING
|
||||
"A debug flag meant for enabling/disabling backend debugging paths"
|
||||
)
|
||||
|
||||
set(FILAMENT_OSMESA_PATH "" CACHE STRING
|
||||
"Path to the OSMesa header and lib"
|
||||
)
|
||||
|
||||
# Enable exceptions by default in spirv-cross.
|
||||
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
|
||||
|
||||
@@ -132,12 +138,22 @@ else()
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
|
||||
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
|
||||
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
|
||||
endif()
|
||||
set(FILAMENT_SUPPORTS_OSMESA TRUE)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_WAYLAND)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WAYLAND)
|
||||
set(FILAMENT_SUPPORTS_X11 FALSE)
|
||||
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
set(FILAMENT_SUPPORTS_X11 FALSE)
|
||||
elseif (FILAMENT_SUPPORTS_OSMESA)
|
||||
set(FILAMENT_SUPPORTS_X11 FALSE)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_OSMESA)
|
||||
else ()
|
||||
if (FILAMENT_SUPPORTS_XCB)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_XCB)
|
||||
|
||||
@@ -7,5 +7,3 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Add support for multi-layered render target with array textures.
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.54.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.55.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.54.2'
|
||||
pod 'Filament', '~> 1.55.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
+14
-1
@@ -7,6 +7,19 @@ 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.55.1
|
||||
|
||||
|
||||
## v1.55.0
|
||||
- Add descriptor sets to describe shader resources. [⚠️ **New Material Version**]
|
||||
|
||||
## v1.54.5
|
||||
|
||||
|
||||
## v1.54.4
|
||||
|
||||
- Add support for multi-layered render target with array textures.
|
||||
|
||||
## v1.54.3
|
||||
|
||||
|
||||
@@ -641,7 +654,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}"
|
||||
}
|
||||
|
||||
|
||||
@@ -520,13 +520,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*,
|
||||
jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB,
|
||||
jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB,
|
||||
jlong jobSystemThreadCount,
|
||||
jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile,
|
||||
jlong jobSystemThreadCount, jboolean disableParallelShaderCompile,
|
||||
jint stereoscopicType, jlong stereoscopicEyeCount,
|
||||
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
|
||||
jboolean disableHandleUseAfterFreeCheck,
|
||||
jint preferredShaderLanguage,
|
||||
jboolean forceGLES2Context) {
|
||||
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
|
||||
@@ -535,7 +534,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
|
||||
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
|
||||
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
|
||||
.textureUseAfterFreePoolSize = (uint32_t) textureUseAfterFreePoolSize,
|
||||
.disableParallelShaderCompile = (bool) disableParallelShaderCompile,
|
||||
.stereoscopicType = (Engine::StereoscopicType) stereoscopicType,
|
||||
.stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount,
|
||||
@@ -544,6 +542,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck,
|
||||
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
|
||||
.forceGLES2Context = (bool) forceGLES2Context,
|
||||
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
|
||||
};
|
||||
builder->config(&config);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -224,13 +224,12 @@ public class Engine {
|
||||
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
|
||||
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
|
||||
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
|
||||
config.jobSystemThreadCount,
|
||||
config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile,
|
||||
config.jobSystemThreadCount, config.disableParallelShaderCompile,
|
||||
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
|
||||
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
|
||||
config.disableHandleUseAfterFreeCheck,
|
||||
config.preferredShaderLanguage.ordinal(),
|
||||
config.forceGLES2Context);
|
||||
config.forceGLES2Context, config.assertNativeWindowIsValid);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -419,12 +418,12 @@ public class Engine {
|
||||
*/
|
||||
public long stereoscopicEyeCount = 2;
|
||||
|
||||
/*
|
||||
/**
|
||||
* @Deprecated This value is no longer used.
|
||||
*/
|
||||
public long resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
/**
|
||||
* This value determines how many frames texture entries are kept for in the cache. This
|
||||
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
|
||||
* Typically only one texture is evicted per frame.
|
||||
@@ -432,12 +431,12 @@ public class Engine {
|
||||
*/
|
||||
public long resourceAllocatorCacheMaxAge = 1;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Disable backend handles use-after-free checks.
|
||||
*/
|
||||
public boolean disableHandleUseAfterFreeCheck = false;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Sets a preferred shader language for Filament to use.
|
||||
*
|
||||
* The Metal backend supports two shader languages: MSL (Metal Shading Language) and
|
||||
@@ -459,12 +458,19 @@ public class Engine {
|
||||
};
|
||||
public ShaderLanguage preferredShaderLanguage = ShaderLanguage.DEFAULT;
|
||||
|
||||
/*
|
||||
/**
|
||||
* When the OpenGL ES backend is used, setting this value to true will force a GLES2.0
|
||||
* context if supported by the Platform, or if not, will have the backend pretend
|
||||
* it's a GLES2 context. Ignored on other backends.
|
||||
*/
|
||||
public boolean forceGLES2Context = false;
|
||||
|
||||
/**
|
||||
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
|
||||
* This is only supported for:
|
||||
* - PlatformEGLAndroid
|
||||
*/
|
||||
public boolean assertNativeWindowIsValid = false;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine, Config config) {
|
||||
@@ -1406,12 +1412,11 @@ public class Engine {
|
||||
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
|
||||
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
|
||||
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
|
||||
long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile,
|
||||
int stereoscopicType, long stereoscopicEyeCount,
|
||||
boolean disableParallelShaderCompile, int stereoscopicType, long stereoscopicEyeCount,
|
||||
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
|
||||
boolean disableHandleUseAfterFreeCheck,
|
||||
int preferredShaderLanguage,
|
||||
boolean forceGLES2Context);
|
||||
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
|
||||
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);
|
||||
|
||||
@@ -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.54.2
|
||||
VERSION_NAME=1.55.0
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ function print_help {
|
||||
echo " enabling debug paths in the backend from the build script. For example, make a"
|
||||
echo " systrace-enabled build without directly changing #defines. Remember to add -f when"
|
||||
echo " changing this option."
|
||||
echo " -X osmesa_path"
|
||||
echo " Indicates a path to a completed OSMesa build. OSMesa is used to create an offscreen GL"
|
||||
echo " context for software rasterization"
|
||||
echo " -S type"
|
||||
echo " Enable stereoscopic rendering where type is one of [instanced|multiview]. This is only"
|
||||
echo " meant for building the samples."
|
||||
@@ -180,6 +183,8 @@ BACKEND_DEBUG_FLAG_OPTION=""
|
||||
|
||||
STEREOSCOPIC_OPTION=""
|
||||
|
||||
OSMESA_OPTION=""
|
||||
|
||||
IOS_BUILD_SIMULATOR=false
|
||||
BUILD_UNIVERSAL_LIBRARIES=false
|
||||
|
||||
@@ -240,6 +245,7 @@ function build_desktop_target {
|
||||
${ASAN_UBSAN_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
${STEREOSCOPIC_OPTION} \
|
||||
${OSMESA_OPTION} \
|
||||
${architectures} \
|
||||
../..
|
||||
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
|
||||
@@ -796,7 +802,7 @@ function check_debug_release_build {
|
||||
|
||||
pushd "$(dirname "$0")" > /dev/null
|
||||
|
||||
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
|
||||
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:X:" opt; do
|
||||
case ${opt} in
|
||||
h)
|
||||
print_help
|
||||
@@ -950,6 +956,8 @@ while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
|
||||
exit 1
|
||||
esac
|
||||
;;
|
||||
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -${OPTARG}" >&2
|
||||
echo ""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/bash
|
||||
if [ `uname` == "Linux" ];then
|
||||
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-linux.zip
|
||||
unzip -q ninja-linux.zip
|
||||
source `dirname $0`/../linux/ci-common.sh
|
||||
elif [ `uname` == "Darwin" ];then
|
||||
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-mac.zip
|
||||
unzip -q ninja-mac.zip
|
||||
@@ -13,9 +12,6 @@ fi
|
||||
chmod +x ninja
|
||||
export PATH="$PWD:$PATH"
|
||||
|
||||
# FIXME: kokoro machines have node and npm but currently they are symlinked to non-existent files
|
||||
# npm install -g typescript
|
||||
|
||||
# Install emscripten.
|
||||
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.15.zip > emsdk.zip
|
||||
unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk
|
||||
|
||||
@@ -115,17 +115,23 @@ cmake ..\.. ^
|
||||
-DFILAMENT_SUPPORTS_VULKAN=ON ^
|
||||
|| exit /b
|
||||
|
||||
set build_flags=-j %NUMBER_OF_PROCESSORS%
|
||||
|
||||
@echo on
|
||||
|
||||
:: we've upgraded the windows machines, so the following are no longer accurate as of 09/19/24, but
|
||||
:: keeping around the comment for record.
|
||||
|
||||
:: Attempt to fix "error C1060: compiler is out of heap space" seen on CI.
|
||||
:: Some resource libraries require significant heap space to compile, so first compile them serially.
|
||||
@echo on
|
||||
cmake --build . --target filagui --config %config% || exit /b
|
||||
cmake --build . --target uberarchive --config %config% || exit /b
|
||||
cmake --build . --target gltf-demo-resources --config %config% || exit /b
|
||||
cmake --build . --target filamentapp-resources --config %config% || exit /b
|
||||
cmake --build . --target sample-resources --config %config% || exit /b
|
||||
cmake --build . --target suzanne-resources --config %config% || exit /b
|
||||
:: cmake --build . --target filagui --config %config% %build_flags% || exit /b
|
||||
:: cmake --build . --target uberarchive --config %config% %build_flags% || exit /b
|
||||
:: cmake --build . --target gltf-demo-resources --config %config% %build_flags% || exit /b
|
||||
:: cmake --build . --target filamentapp-resources --config %config% %build_flags% || exit /b
|
||||
:: cmake --build . --target sample-resources --config %config% %build_flags% || exit /b
|
||||
:: cmake --build . --target suzanne-resources --config %config% %build_flags% || exit /b
|
||||
|
||||
cmake --build . %INSTALL% --config %config% -- /m || exit /b
|
||||
cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
|
||||
@echo off
|
||||
|
||||
echo Disk info after building variant: %variant%
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -66,6 +66,7 @@ set(SRCS
|
||||
src/FrameSkipper.cpp
|
||||
src/Froxelizer.cpp
|
||||
src/Frustum.cpp
|
||||
src/HwDescriptorSetLayoutFactory.cpp
|
||||
src/HwRenderPrimitiveFactory.cpp
|
||||
src/HwVertexBufferInfoFactory.cpp
|
||||
src/IndexBuffer.cpp
|
||||
@@ -153,6 +154,7 @@ set(PRIVATE_HDRS
|
||||
src/FrameInfo.h
|
||||
src/FrameSkipper.h
|
||||
src/Froxelizer.h
|
||||
src/HwDescriptorSetLayoutFactory.h
|
||||
src/HwRenderPrimitiveFactory.h
|
||||
src/HwVertexBufferInfoFactory.h
|
||||
src/Intersections.h
|
||||
@@ -605,6 +607,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
|
||||
|
||||
@@ -119,6 +119,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformGLX.cpp)
|
||||
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformEGLHeadless.cpp)
|
||||
elseif (FILAMENT_SUPPORTS_OSMESA)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformOSMesa.cpp)
|
||||
endif()
|
||||
elseif (WIN32)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformWGL.cpp)
|
||||
@@ -366,6 +368,15 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS
|
||||
-Wl,--exclude-libs,bluegl
|
||||
)
|
||||
|
||||
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)
|
||||
set(FILAMENT_WARNINGS /W3)
|
||||
else()
|
||||
@@ -377,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)
|
||||
@@ -386,6 +400,7 @@ endif()
|
||||
|
||||
target_compile_options(${TARGET} PRIVATE
|
||||
${FILAMENT_WARNINGS}
|
||||
${OSMESA_COMPILE_FLAGS}
|
||||
$<$<CONFIG:Release>:${OPTIMIZATION_FLAGS}>
|
||||
$<$<AND:$<PLATFORM_ID:Darwin>,$<CONFIG:Release>>:${DARWIN_OPTIMIZATION_FLAGS}>
|
||||
)
|
||||
@@ -395,6 +410,7 @@ if (FILAMENT_SUPPORTS_METAL)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TARGET} PRIVATE
|
||||
${OSMESA_LINKER_FLAGS}
|
||||
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
|
||||
)
|
||||
|
||||
@@ -429,6 +445,7 @@ if (APPLE OR LINUX)
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
test/test_Handles.cpp
|
||||
)
|
||||
set(BACKEND_TEST_LIBS
|
||||
backend
|
||||
@@ -526,6 +543,8 @@ if (APPLE AND NOT IOS)
|
||||
|
||||
add_executable(metal_utils_test test/MetalTest.mm)
|
||||
|
||||
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
|
||||
|
||||
target_link_libraries(metal_utils_test PRIVATE
|
||||
backend
|
||||
getopt
|
||||
|
||||
@@ -242,8 +242,17 @@ struct DescriptorSetLayoutBinding {
|
||||
DescriptorType type;
|
||||
ShaderStageFlags stageFlags;
|
||||
descriptor_binding_t binding;
|
||||
DescriptorFlags flags;
|
||||
uint16_t count;
|
||||
DescriptorFlags flags = DescriptorFlags::NONE;
|
||||
uint16_t count = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
DescriptorSetLayoutBinding const& lhs,
|
||||
DescriptorSetLayoutBinding const& rhs) noexcept {
|
||||
return lhs.type == rhs.type &&
|
||||
lhs.flags == rhs.flags &&
|
||||
lhs.count == rhs.count &&
|
||||
lhs.stageFlags == rhs.stageFlags;
|
||||
}
|
||||
};
|
||||
|
||||
struct DescriptorSetLayout {
|
||||
@@ -309,8 +318,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.
|
||||
@@ -756,7 +776,8 @@ enum class TextureUsage : uint16_t {
|
||||
BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit()
|
||||
BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit()
|
||||
PROTECTED = 0x0100, //!< Texture can be used for protected content
|
||||
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
|
||||
DEFAULT = UPLOADABLE | SAMPLEABLE, //!< Default texture usage
|
||||
ALL_ATTACHMENTS = COLOR_ATTACHMENT | DEPTH_ATTACHMENT | STENCIL_ATTACHMENT | SUBPASS_INPUT, //!< Mask of all attachments
|
||||
};
|
||||
|
||||
inline const char* stringify(TextureUsage usage) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <type_traits> // FIXME: STL headers are not allowed in public headers
|
||||
#include <utility>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -106,8 +107,18 @@ struct Handle : public HandleBase {
|
||||
Handle(Handle const& rhs) noexcept = default;
|
||||
Handle(Handle&& rhs) noexcept = default;
|
||||
|
||||
Handle& operator=(Handle const& rhs) noexcept = default;
|
||||
Handle& operator=(Handle&& rhs) noexcept = default;
|
||||
// Explicitly redefine copy/move assignment operators rather than just using default here.
|
||||
// Because it doesn't make a call to the parent's method automatically during the std::move
|
||||
// function call(https://en.cppreference.com/w/cpp/algorithm/move) in certain compilers like
|
||||
// NDK 25.1.8937393 and below (see b/371980551)
|
||||
Handle& operator=(Handle const& rhs) noexcept {
|
||||
HandleBase::operator=(rhs);
|
||||
return *this;
|
||||
}
|
||||
Handle& operator=(Handle&& rhs) noexcept {
|
||||
HandleBase::operator=(std::move(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit Handle(HandleId id) noexcept : HandleBase(id) { }
|
||||
|
||||
|
||||
@@ -68,13 +68,6 @@ public:
|
||||
*/
|
||||
size_t handleArenaSize = 0;
|
||||
|
||||
/**
|
||||
* This number of most-recently destroyed textures will be tracked for use-after-free.
|
||||
* Throws an exception when a texture is freed but still bound to a SamplerGroup and used in
|
||||
* a draw call. 0 disables completely. Currently only respected by the Metal backend.
|
||||
*/
|
||||
size_t textureUseAfterFreePoolSize = 0;
|
||||
|
||||
size_t metalUploadBufferSizeBytes = 512 * 1024;
|
||||
|
||||
/**
|
||||
@@ -98,6 +91,13 @@ public:
|
||||
* Sets the technique for stereoscopic rendering.
|
||||
*/
|
||||
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
||||
|
||||
/**
|
||||
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
|
||||
* This is only supported for:
|
||||
* - PlatformEGLAndroid
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -30,10 +30,6 @@ namespace filament::backend {
|
||||
|
||||
struct TargetBufferInfo {
|
||||
// note: the parameters of this constructor are not in the order of this structure's fields
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept
|
||||
: handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) {
|
||||
}
|
||||
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
|
||||
: handle(handle), level(level), layer(layer) {
|
||||
}
|
||||
@@ -51,14 +47,15 @@ struct TargetBufferInfo {
|
||||
// texture to be used as render target
|
||||
Handle<HwTexture> handle;
|
||||
|
||||
// Starting layer index for multiview. This value is only used when the `layerCount` for the
|
||||
// render target is greater than 1.
|
||||
uint8_t baseViewIndex = 0;
|
||||
|
||||
// level to be used
|
||||
uint8_t level = 0;
|
||||
|
||||
// For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
|
||||
// - For cubemap textures, this indicates the face of the cubemap. See TextureCubemapFace for
|
||||
// the face->layer mapping)
|
||||
// - For 2d array, cubemap array, and 3d textures, this indicates an index of a single layer of
|
||||
// them.
|
||||
// - For multiview textures (i.e., layerCount for the RenderTarget is greater than 1), this
|
||||
// indicates a starting layer index of the current 2d array texture for multiview.
|
||||
uint16_t layer = 0;
|
||||
};
|
||||
|
||||
@@ -103,7 +100,7 @@ public:
|
||||
|
||||
// this is here for backward compatibility
|
||||
MRT(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
|
||||
: mInfos{{ handle, level, layer, 0 }} {
|
||||
: mInfos{{ handle, level, layer }} {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -89,6 +89,11 @@ protected:
|
||||
*/
|
||||
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
|
||||
|
||||
protected:
|
||||
bool makeCurrent(ContextType type,
|
||||
SwapChain* drawSwapChain,
|
||||
SwapChain* readSwapChain) noexcept override;
|
||||
|
||||
private:
|
||||
struct InitializeJvmForPerformanceManagerIfNeeded {
|
||||
InitializeJvmForPerformanceManagerIfNeeded();
|
||||
@@ -102,6 +107,10 @@ private:
|
||||
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
clock::time_point mStartTimeOfActualWork;
|
||||
|
||||
void* mNativeWindowLib = nullptr;
|
||||
int32_t (*ANativeWindow_getBuffersDefaultDataSpace)(ANativeWindow* window) = nullptr;
|
||||
bool mAssertNativeWindowIsValid = false;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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_OPENGL_OPENGL_PLATFORM_OSMESA_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bluegl/BlueGL.h"
|
||||
|
||||
#include "osmesa.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A concrete implementation of OpenGLPlatform that uses OSMesa, which is an offscreen
|
||||
* context that can be used in conjunction with Mesa for software rasterization.
|
||||
* See https://docs.mesa3d.org/osmesa.html for more information.
|
||||
*/
|
||||
class PlatformOSMesa : public OpenGLPlatform {
|
||||
protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Platform Interface
|
||||
|
||||
Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) noexcept override;
|
||||
|
||||
int getOSVersion() const noexcept final override { return 0; }
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// OpenGLPlatform Interface
|
||||
|
||||
void terminate() noexcept override;
|
||||
|
||||
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
|
||||
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
|
||||
void destroySwapChain(SwapChain* swapChain) noexcept override;
|
||||
bool makeCurrent(ContextType type, SwapChain* drawSwapChain,
|
||||
SwapChain* readSwapChain) noexcept override;
|
||||
void commit(SwapChain* swapChain) noexcept override;
|
||||
|
||||
private:
|
||||
OSMesaContext mContext;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
|
||||
@@ -88,6 +88,7 @@ public:
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
bool protection = 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();
|
||||
|
||||
|
||||
@@ -139,7 +139,8 @@ DECL_DRIVER_API_N(beginFrame,
|
||||
DECL_DRIVER_API_N(setFrameScheduledCallback,
|
||||
backend::SwapChainHandle, sch,
|
||||
backend::CallbackHandler*, handler,
|
||||
backend::FrameScheduledCallback&&, callback)
|
||||
backend::FrameScheduledCallback&&, callback,
|
||||
uint64_t, flags)
|
||||
|
||||
DECL_DRIVER_API_N(setFrameCompletedCallback,
|
||||
backend::SwapChainHandle, sch,
|
||||
|
||||
@@ -169,7 +169,7 @@ public:
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
|
||||
if (isPoolHandle(handle.getId())) {
|
||||
// check for use after free
|
||||
// check for pool handle use-after-free
|
||||
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
|
||||
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
|
||||
auto const pNode = static_cast<typename Allocator::Node*>(p);
|
||||
@@ -179,6 +179,22 @@ public:
|
||||
<< "use-after-free of Handle with id=" << handle.getId()
|
||||
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
|
||||
}
|
||||
} else {
|
||||
// check for heap handle use-after-free
|
||||
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
|
||||
uint8_t const index = (handle.getId() & HANDLE_INDEX_MASK);
|
||||
// if we've already handed out this handle index before, it's definitely a
|
||||
// use-after-free, otherwise it's probably just a corrupted handle
|
||||
if (index < mId) {
|
||||
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
|
||||
<< "use-after-free of heap Handle with id=" << handle.getId()
|
||||
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
|
||||
} else {
|
||||
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
|
||||
<< "corrupted heap Handle with id=" << handle.getId()
|
||||
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<Dp>(p);
|
||||
@@ -186,14 +202,18 @@ public:
|
||||
|
||||
template<typename B>
|
||||
bool is_valid(Handle<B>& handle) {
|
||||
if (handle && isPoolHandle(handle.getId())) {
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
if (!handle) {
|
||||
// null handles are invalid
|
||||
return false;
|
||||
}
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
if (isPoolHandle(handle.getId())) {
|
||||
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
|
||||
auto const pNode = static_cast<typename Allocator::Node*>(p);
|
||||
uint8_t const expectedAge = pNode[-1].age;
|
||||
return expectedAge == age;
|
||||
}
|
||||
return true;
|
||||
return p != nullptr;
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformEGLHeadless.h"
|
||||
#endif
|
||||
#elif defined(FILAMENT_SUPPORTS_OSMESA)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformOSMesa.h"
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
@@ -124,6 +128,8 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
return new PlatformGLX();
|
||||
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
return new PlatformEGLHeadless();
|
||||
#elif defined(FILAMENT_SUPPORTS_OSMESA)
|
||||
return new PlatformOSMesa();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
@@ -73,10 +73,11 @@ public:
|
||||
enum class Type {
|
||||
NONE = 0,
|
||||
GENERIC = 1,
|
||||
RING = 2,
|
||||
RING = 2, // deprecated
|
||||
STAGING = 3,
|
||||
DESCRIPTOR_SET = 4,
|
||||
};
|
||||
static constexpr size_t TypeCount = 3;
|
||||
static constexpr size_t TypeCount = 4;
|
||||
|
||||
static constexpr auto toIndex(Type t) {
|
||||
assert_invariant(t != Type::NONE);
|
||||
@@ -88,6 +89,8 @@ public:
|
||||
return 1;
|
||||
case Type::STAGING:
|
||||
return 2;
|
||||
case Type::DESCRIPTOR_SET:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +174,10 @@ public:
|
||||
* Update the buffer with data inside src. Potentially allocates a new buffer allocation to hold
|
||||
* the bytes which will be released when the current frame is finished.
|
||||
*/
|
||||
void copyIntoBuffer(void* src, size_t size, size_t byteOffset);
|
||||
void copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset);
|
||||
using TagResolver = utils::Invocable<const char*(void)>;
|
||||
void copyIntoBuffer(void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag);
|
||||
void copyIntoBufferUnsynchronized(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag);
|
||||
|
||||
/**
|
||||
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
|
||||
@@ -211,8 +216,10 @@ private:
|
||||
BUMP_ALLOCATOR,
|
||||
};
|
||||
|
||||
void uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const;
|
||||
void uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const;
|
||||
void uploadWithPoolBuffer(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const;
|
||||
void uploadWithBumpAllocator(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const;
|
||||
|
||||
UploadStrategy mUploadStrategy;
|
||||
TrackedMetalBuffer mBuffer;
|
||||
|
||||
@@ -66,15 +66,20 @@ MetalBuffer::~MetalBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
|
||||
void MetalBuffer::copyIntoBuffer(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(src)
|
||||
<< "copyIntoBuffer called with a null src, tag=" << getHandleTag();
|
||||
FILAMENT_CHECK_PRECONDITION(size + byteOffset <= mBufferSize)
|
||||
<< "Attempting to copy " << size << " bytes into a buffer of size " << mBufferSize
|
||||
<< " at offset " << byteOffset;
|
||||
<< " at offset " << byteOffset << ", tag=" << getHandleTag();
|
||||
// The copy blit requires that byteOffset be a multiple of 4.
|
||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) << "byteOffset must be a multiple of 4";
|
||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
|
||||
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
|
||||
|
||||
// If we have a cpu buffer, we can directly copy into it.
|
||||
if (mCpuBuffer) {
|
||||
@@ -84,17 +89,18 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
|
||||
|
||||
switch (mUploadStrategy) {
|
||||
case UploadStrategy::BUMP_ALLOCATOR:
|
||||
uploadWithBumpAllocator(src, size, byteOffset);
|
||||
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
|
||||
break;
|
||||
case UploadStrategy::POOL:
|
||||
uploadWithPoolBuffer(src, size, byteOffset);
|
||||
uploadWithPoolBuffer(src, size, byteOffset, std::move(getHandleTag));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset) {
|
||||
void MetalBuffer::copyIntoBufferUnsynchronized(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
||||
// TODO: implement the unsynchronized version
|
||||
copyIntoBuffer(src, size, byteOffset);
|
||||
copyIntoBuffer(src, size, byteOffset, std::move(getHandleTag));
|
||||
}
|
||||
|
||||
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
|
||||
@@ -200,9 +206,13 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
|
||||
}
|
||||
}
|
||||
|
||||
void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const {
|
||||
void MetalBuffer::uploadWithPoolBuffer(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
|
||||
MetalBufferPool* bufferPool = mContext.bufferPool;
|
||||
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
|
||||
FILAMENT_CHECK_POSTCONDITION(staging)
|
||||
<< "uploadWithPoolbuffer unable to acquire staging buffer of size " << size
|
||||
<< ", tag=" << getHandleTag();
|
||||
memcpy(staging->buffer.get().contents, src, size);
|
||||
|
||||
// Encode a blit from the staging buffer into the private GPU buffer.
|
||||
@@ -220,10 +230,18 @@ void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset
|
||||
}];
|
||||
}
|
||||
|
||||
void MetalBuffer::uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const {
|
||||
void MetalBuffer::uploadWithBumpAllocator(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
|
||||
MetalBumpAllocator& allocator = *mContext.bumpAllocator;
|
||||
auto [buffer, offset] = allocator.allocateStagingArea(size);
|
||||
memcpy(static_cast<char*>(buffer.contents) + offset, src, size);
|
||||
FILAMENT_CHECK_POSTCONDITION(buffer)
|
||||
<< "uploadWithBumpAllocator unable to acquire staging area of size " << size
|
||||
<< ", tag=" << getHandleTag();
|
||||
void* const contents = buffer.contents;
|
||||
FILAMENT_CHECK_POSTCONDITION(contents)
|
||||
<< "uploadWithBumpAllocator unable to acquire pointer to staging area, size " << size
|
||||
<< ", tag=" << getHandleTag();
|
||||
memcpy(static_cast<char*>(contents) + offset, src, size);
|
||||
|
||||
// Encode a blit from the staging buffer into the private GPU buffer.
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
|
||||
|
||||
@@ -118,9 +118,6 @@ private:
|
||||
};
|
||||
|
||||
struct MetalContext {
|
||||
explicit MetalContext(size_t metalFreedTextureListSize)
|
||||
: texturesToDestroy(metalFreedTextureListSize) {}
|
||||
|
||||
MetalDriver* driver;
|
||||
id<MTLDevice> device = nullptr;
|
||||
id<MTLCommandQueue> commandQueue = nullptr;
|
||||
@@ -164,6 +161,7 @@ struct MetalContext {
|
||||
CullModeStateTracker cullModeState;
|
||||
WindingStateTracker windingState;
|
||||
DepthClampStateTracker depthClampState;
|
||||
ScissorRectStateTracker scissorRectState;
|
||||
Handle<HwRenderPrimitive> currentRenderPrimitive;
|
||||
|
||||
// State caches.
|
||||
@@ -187,14 +185,6 @@ struct MetalContext {
|
||||
// Keeps track of all alive textures.
|
||||
tsl::robin_set<MetalTexture*> textures;
|
||||
|
||||
// This circular buffer implements delayed destruction for Metal texture handles. It keeps a
|
||||
// handle to a fixed number of the most recently destroyed texture handles. When we're asked to
|
||||
// destroy a texture handle, we free its texture memory, but keep the MetalTexture object alive,
|
||||
// marking it as "terminated". If we later are asked to use that texture, we can check its
|
||||
// terminated status and throw an Objective-C error instead of crashing, which is helpful for
|
||||
// debugging use-after-free issues in release builds.
|
||||
utils::FixedCircularBuffer<Handle<HwTexture>> texturesToDestroy;
|
||||
|
||||
MetalBufferPool* bufferPool;
|
||||
MetalBumpAllocator* bumpAllocator;
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "CommandStreamDispatcher.h"
|
||||
#include "metal/MetalDriver.h"
|
||||
|
||||
#include <filament/SwapChain.h>
|
||||
|
||||
#include "MetalBlitter.h"
|
||||
#include "MetalBufferPool.h"
|
||||
#include "MetalContext.h"
|
||||
@@ -38,6 +40,7 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/sstream.h>
|
||||
#include <utils/Invocable.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -105,13 +108,13 @@ Dispatcher MetalDriver::getDispatcher() const noexcept {
|
||||
return ConcreteDispatcher<MetalDriver>::make();
|
||||
}
|
||||
|
||||
MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(*platform),
|
||||
mContext(new MetalContext(driverConfig.textureUseAfterFreePoolSize)),
|
||||
mHandleAllocator("Handles",
|
||||
driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
MetalDriver::MetalDriver(
|
||||
MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(*platform),
|
||||
mContext(new MetalContext),
|
||||
mHandleAllocator(
|
||||
"Handles", driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
mContext->driver = this;
|
||||
|
||||
TrackedMetalBuffer::setPlatform(platform);
|
||||
@@ -251,13 +254,19 @@ void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::RING));
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging",
|
||||
TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::STAGING));
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.descriptor_set",
|
||||
TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::DESCRIPTOR_SET));
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::setFrameScheduledCallback(
|
||||
Handle<HwSwapChain> sch, CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
||||
FrameScheduledCallback&& callback, uint64_t flags) {
|
||||
// Turn off the CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER flag if a custom handler is provided.
|
||||
if (handler) {
|
||||
flags &= ~SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER;
|
||||
}
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
swapChain->setFrameScheduledCallback(handler, std::move(callback));
|
||||
swapChain->setFrameScheduledCallback(handler, std::move(callback), flags);
|
||||
}
|
||||
|
||||
void MetalDriver::setFrameCompletedCallback(
|
||||
@@ -517,6 +526,11 @@ void MetalDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
}
|
||||
|
||||
void MetalDriver::createProgramR(Handle<HwProgram> rph, Program&& program) {
|
||||
#if FILAMENT_METAL_DEBUG_LOG
|
||||
auto handleId = rph.getId();
|
||||
DEBUG_LOG("createProgramR(rph = %d, program = ", handleId);
|
||||
utils::slog.d << program << utils::io::endl;
|
||||
#endif
|
||||
construct_handle<MetalProgram>(rph, *mContext, std::move(program));
|
||||
}
|
||||
|
||||
@@ -650,10 +664,9 @@ void MetalDriver::createDescriptorSetLayoutR(
|
||||
[](const auto& a, const auto& b) { return a.binding < b.binding; });
|
||||
DEBUG_LOG("createDescriptorSetLayoutR(dslh = %d, info = {\n", dslh.getId());
|
||||
for (size_t i = 0; i < info.bindings.size(); i++) {
|
||||
DEBUG_LOG(" {binding = %d, type = %s, count = %d, stage = %s, flags = %s}",
|
||||
DEBUG_LOG(" {binding = %d, type = %s, count = %d, stage = %s, flags = %s},\n",
|
||||
info.bindings[i].binding, toString(info.bindings[i].type), info.bindings[i].count,
|
||||
toString(info.bindings[i].stageFlags), toString(info.bindings[i].flags));
|
||||
DEBUG_LOG(",\n");
|
||||
}
|
||||
DEBUG_LOG("})\n");
|
||||
construct_handle<MetalDescriptorSetLayout>(dslh, std::move(info));
|
||||
@@ -796,15 +809,7 @@ void MetalDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
auto* metalTexture = handle_cast<MetalTexture>(th);
|
||||
mContext->textures.erase(metalTexture);
|
||||
|
||||
// Free memory from the texture and mark it as freed.
|
||||
metalTexture->terminate();
|
||||
|
||||
// Add this texture handle to our texturesToDestroy queue to be destroyed later.
|
||||
if (auto handleToFree = mContext->texturesToDestroy.push(th)) {
|
||||
// If texturesToDestroy is full, then .push evicts the oldest texture handle in the
|
||||
// queue (or simply th, if use-after-free detection is disabled).
|
||||
destruct_handle<MetalTexture>(handleToFree.value());
|
||||
}
|
||||
destruct_handle<MetalTexture>(th);
|
||||
}
|
||||
|
||||
void MetalDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
@@ -854,12 +859,6 @@ void MetalDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
||||
}
|
||||
|
||||
void MetalDriver::terminate() {
|
||||
// Terminate any outstanding MetalTextures.
|
||||
while (!mContext->texturesToDestroy.empty()) {
|
||||
Handle<HwTexture> toDestroy = mContext->texturesToDestroy.pop();
|
||||
destruct_handle<MetalTexture>(toDestroy);
|
||||
}
|
||||
|
||||
// finish() will flush the pending command buffer and will ensure all GPU work has finished.
|
||||
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
|
||||
finish();
|
||||
@@ -1083,24 +1082,34 @@ size_t MetalDriver::getMaxUniformBufferSize() {
|
||||
|
||||
void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& data,
|
||||
uint32_t byteOffset) {
|
||||
FILAMENT_CHECK_PRECONDITION(data.buffer)
|
||||
<< "updateIndexBuffer called with a null buffer.";
|
||||
auto* ib = handle_cast<MetalIndexBuffer>(ibh);
|
||||
ib->buffer.copyIntoBuffer(data.buffer, data.size, byteOffset);
|
||||
ib->buffer.copyIntoBuffer(data.buffer, data.size, byteOffset,
|
||||
[&]() { return mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe(); });
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
|
||||
uint32_t byteOffset) {
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "updateBufferObject must be called outside of a render pass.";
|
||||
<< "updateBufferObject must be called outside of a render pass. tag="
|
||||
<< mHandleAllocator.getHandleTag(boh.getId()).c_str_safe();
|
||||
FILAMENT_CHECK_PRECONDITION(data.buffer)
|
||||
<< "updateBufferObject called with a null buffer. tag="
|
||||
<< mHandleAllocator.getHandleTag(boh.getId()).c_str_safe();
|
||||
auto* bo = handle_cast<MetalBufferObject>(boh);
|
||||
bo->updateBuffer(data.buffer, data.size, byteOffset);
|
||||
|
||||
bo->updateBuffer(data.buffer, data.size, byteOffset,
|
||||
[&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); });
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
void MetalDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
|
||||
BufferDescriptor&& data, uint32_t byteOffset) {
|
||||
auto* bo = handle_cast<MetalBufferObject>(boh);
|
||||
bo->updateBufferUnsynchronized(data.buffer, data.size, byteOffset);
|
||||
bo->updateBufferUnsynchronized(data.buffer, data.size, byteOffset,
|
||||
[&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); });
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
@@ -1218,6 +1227,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();
|
||||
@@ -1596,6 +1606,7 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
||||
<< "bindPipeline() without a valid command encoder.";
|
||||
DEBUG_LOG("bindPipeline(ps = { program = %d }))\n", ps.program.getId());
|
||||
|
||||
MetalVertexBufferInfo const* const vbi =
|
||||
handle_cast<MetalVertexBufferInfo>(ps.vertexBufferInfo);
|
||||
@@ -1787,7 +1798,7 @@ void MetalDriver::bindDescriptorSet(
|
||||
const size_t dynamicBindings = descriptorSet->layout->getDynamicOffsetCount();
|
||||
utils::FixedCapacityVector<size_t> offsetsVector(dynamicBindings, 0);
|
||||
#if FILAMENT_METAL_DEBUG_LOG == 1
|
||||
DEBUG_LOG("bindDescriptorSet(dsh = %d, set = %d, offsets = [", dsh.getId(), set);
|
||||
printf("[METAL DEBUG] bindDescriptorSet(dsh = %d, set = %d, offsets = [", dsh.getId(), set);
|
||||
for (size_t i = 0; i < dynamicBindings; i++) {
|
||||
printf("%d", offsets[i]);
|
||||
if (i < dynamicBindings - 1) {
|
||||
@@ -1796,7 +1807,7 @@ void MetalDriver::bindDescriptorSet(
|
||||
|
||||
offsetsVector[i] = offsets[i];
|
||||
}
|
||||
DEBUG_LOG("])\n");
|
||||
printf("])\n");
|
||||
#endif
|
||||
|
||||
// Bind the descriptor set.
|
||||
@@ -1943,7 +1954,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 {
|
||||
|
||||
@@ -74,7 +74,8 @@ public:
|
||||
|
||||
void releaseDrawable();
|
||||
|
||||
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
|
||||
void setFrameScheduledCallback(
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
|
||||
void setFrameCompletedCallback(
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
|
||||
|
||||
@@ -123,6 +124,7 @@ private:
|
||||
struct {
|
||||
CallbackHandler* handler = nullptr;
|
||||
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
|
||||
uint64_t flags = 0;
|
||||
} frameScheduled;
|
||||
|
||||
struct {
|
||||
@@ -133,11 +135,15 @@ private:
|
||||
|
||||
class MetalBufferObject : public HwBufferObject {
|
||||
public:
|
||||
|
||||
using TagResolver = MetalBuffer::TagResolver;
|
||||
|
||||
MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
|
||||
uint32_t byteCount);
|
||||
|
||||
void updateBuffer(void* data, size_t size, uint32_t byteOffset);
|
||||
void updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset);
|
||||
void updateBuffer(void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag);
|
||||
void updateBufferUnsynchronized(
|
||||
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag);
|
||||
MetalBuffer* getBuffer() { return &buffer; }
|
||||
|
||||
private:
|
||||
@@ -265,26 +271,6 @@ public:
|
||||
|
||||
MTLPixelFormat devicePixelFormat;
|
||||
|
||||
// Frees memory associated with this texture and marks it as "terminated".
|
||||
// Used to track "use after free" scenario.
|
||||
void terminate() noexcept;
|
||||
bool isTerminated() const noexcept { return terminated; }
|
||||
inline void checkUseAfterFree(const char* samplerGroupDebugName, size_t textureIndex) const {
|
||||
if (UTILS_LIKELY(!isTerminated())) {
|
||||
return;
|
||||
}
|
||||
NSString* reason =
|
||||
[NSString stringWithFormat:
|
||||
@"Filament Metal texture use after free, sampler group = "
|
||||
@"%s, texture index = %zu",
|
||||
samplerGroupDebugName, textureIndex];
|
||||
NSException* useAfterFreeException =
|
||||
[NSException exceptionWithName:@"MetalTextureUseAfterFree"
|
||||
reason:reason
|
||||
userInfo:nil];
|
||||
[useAfterFreeException raise];
|
||||
}
|
||||
|
||||
private:
|
||||
void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
|
||||
PixelBufferDescriptor const& data) noexcept;
|
||||
@@ -301,8 +287,6 @@ private:
|
||||
// Filament swizzling only affects texture reads, so this should not be used when the texture is
|
||||
// bound as a render target attachment.
|
||||
id<MTLTexture> swizzledTextureView = nil;
|
||||
|
||||
bool terminated = false;
|
||||
};
|
||||
|
||||
class MetalRenderTarget : public HwRenderTarget {
|
||||
@@ -511,7 +495,7 @@ struct MetalDescriptorSet : public HwDescriptorSet {
|
||||
|
||||
std::vector<std::shared_ptr<MetalExternalImage>> externalImages;
|
||||
|
||||
std::array<id<MTLBuffer>, Program::SHADER_TYPE_COUNT> cachedBuffer = { nil };
|
||||
std::array<TrackedMetalBuffer, Program::SHADER_TYPE_COUNT> cachedBuffer = { nil };
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
|
||||
@@ -229,9 +229,10 @@ void MetalSwapChain::ensureDepthStencilTexture() {
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameScheduledCallback(
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
|
||||
frameScheduled.handler = handler;
|
||||
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
|
||||
frameScheduled.flags = flags;
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameCompletedCallback(
|
||||
@@ -260,10 +261,10 @@ public:
|
||||
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
|
||||
|
||||
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
|
||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver) {
|
||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags) {
|
||||
assert_invariant(drawableMutex);
|
||||
assert_invariant(driver);
|
||||
return new PresentDrawableData(drawable, drawableMutex, driver);
|
||||
return new PresentDrawableData(drawable, drawableMutex, driver, flags);
|
||||
}
|
||||
|
||||
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
|
||||
@@ -271,16 +272,23 @@ public:
|
||||
[that->mDrawable present];
|
||||
}
|
||||
|
||||
// mDrawable is acquired on the driver thread. Typically, we would release this object on
|
||||
// the same thread, but after receiving consistent crash reports from within
|
||||
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); });
|
||||
if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
|
||||
cleanupAndDestroy(that);
|
||||
} else {
|
||||
// mDrawable is acquired on the driver thread. Typically, we would release this object
|
||||
// on the same thread, but after receiving consistent crash reports from within
|
||||
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main
|
||||
// thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
cleanupAndDestroy(that);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
|
||||
MetalDriver* driver)
|
||||
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver) {}
|
||||
MetalDriver* driver, uint64_t flags)
|
||||
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {}
|
||||
|
||||
static void cleanupAndDestroy(PresentDrawableData *that) {
|
||||
if (that->mDrawable) {
|
||||
@@ -295,6 +303,7 @@ private:
|
||||
id<CAMetalDrawable> mDrawable;
|
||||
std::shared_ptr<std::mutex> mDrawableMutex;
|
||||
MetalDriver* mDriver = nullptr;
|
||||
uint64_t mFlags = 0;
|
||||
};
|
||||
|
||||
void presentDrawable(bool presentFrame, void* user) {
|
||||
@@ -311,8 +320,8 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
|
||||
struct Callback {
|
||||
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
|
||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver)
|
||||
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver)) {}
|
||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags)
|
||||
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {}
|
||||
std::shared_ptr<FrameScheduledCallback> f;
|
||||
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
|
||||
std::unique_ptr<PresentDrawableData> data;
|
||||
@@ -327,14 +336,19 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
|
||||
// This callback pointer will be captured by the block. Even if the scheduled handler is never
|
||||
// called, the unique_ptr will still ensure we don't leak memory.
|
||||
uint64_t const flags = frameScheduled.flags;
|
||||
__block auto callback = std::make_unique<Callback>(
|
||||
frameScheduled.callback, drawable, layerDrawableMutex, context.driver);
|
||||
frameScheduled.callback, drawable, layerDrawableMutex, context.driver, flags);
|
||||
|
||||
backend::CallbackHandler* handler = frameScheduled.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
Callback* user = callback.release();
|
||||
driver->scheduleCallback(handler, user, &Callback::func);
|
||||
if (flags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
|
||||
Callback::func(user);
|
||||
} else {
|
||||
driver->scheduleCallback(handler, user, &Callback::func);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -369,12 +383,14 @@ MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding
|
||||
BufferUsage usage, uint32_t byteCount)
|
||||
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
|
||||
|
||||
void MetalBufferObject::updateBuffer(void* data, size_t size, uint32_t byteOffset) {
|
||||
buffer.copyIntoBuffer(data, size, byteOffset);
|
||||
void MetalBufferObject::updateBuffer(
|
||||
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
|
||||
buffer.copyIntoBuffer(data, size, byteOffset, std::move(getHandleTag));
|
||||
}
|
||||
|
||||
void MetalBufferObject::updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset) {
|
||||
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset);
|
||||
void MetalBufferObject::updateBufferUnsynchronized(
|
||||
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
|
||||
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset, std::move(getHandleTag));
|
||||
}
|
||||
|
||||
MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount,
|
||||
@@ -634,14 +650,6 @@ MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t
|
||||
texture = externalImage->getMtlTexture();
|
||||
}
|
||||
|
||||
void MetalTexture::terminate() noexcept {
|
||||
texture = nil;
|
||||
swizzledTextureView = nil;
|
||||
msaaSidecar = nil;
|
||||
externalImage = nullptr;
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalTexture::getMtlTextureForRead() const noexcept {
|
||||
return swizzledTextureView ? swizzledTextureView : texture;
|
||||
}
|
||||
@@ -1345,7 +1353,7 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
||||
auto& buffer = cachedBuffer[index];
|
||||
|
||||
if (buffer) {
|
||||
return buffer;
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
// Map all the texture bindings to their respective texture types.
|
||||
@@ -1368,9 +1376,13 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
||||
id<MTLArgumentEncoder> encoder =
|
||||
layout->getArgumentEncoder(context.device, stage, textureTypes);
|
||||
|
||||
buffer = [context.device newBufferWithLength:encoder.encodedLength
|
||||
options:MTLResourceStorageModeShared];
|
||||
[encoder setArgumentBuffer:buffer offset:0];
|
||||
{
|
||||
ScopedAllocationTimer timer("descriptor_set");
|
||||
buffer = { [context.device newBufferWithLength:encoder.encodedLength
|
||||
options:MTLResourceStorageModeShared],
|
||||
TrackedMetalBuffer::Type::DESCRIPTOR_SET };
|
||||
}
|
||||
[encoder setArgumentBuffer:buffer.get() offset:0];
|
||||
|
||||
for (auto const& binding : bindings) {
|
||||
if (!hasShaderType(binding.stageFlags, stage)) {
|
||||
@@ -1417,7 +1429,7 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
|
||||
@@ -204,9 +204,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 +213,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 +234,6 @@ private:
|
||||
|
||||
bool mStateDirty = true;
|
||||
StateType mCurrentState = {};
|
||||
|
||||
};
|
||||
|
||||
// Pipeline state
|
||||
@@ -343,6 +341,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;
|
||||
@@ -434,7 +442,7 @@ public:
|
||||
|
||||
private:
|
||||
static_assert(N <= 8);
|
||||
std::array<__unsafe_unretained id<MTLBuffer>, N> mBuffers = { nil };
|
||||
std::array<__weak id<MTLBuffer>, N> mBuffers = { nil };
|
||||
std::array<NSUInteger, N> mOffsets = { 0 };
|
||||
utils::bitset8 mDirtyBuffers;
|
||||
utils::bitset8 mDirtyOffsets;
|
||||
|
||||
@@ -54,7 +54,7 @@ void NoopDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
}
|
||||
|
||||
void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,9 @@ GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle ds
|
||||
bool const dynamicOffset = any(entry.flags & DescriptorFlags::DYNAMIC_OFFSET);
|
||||
dynamicBuffers.set(index, dynamicOffset);
|
||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||
dynamicBufferCount++;
|
||||
if (dynamicOffset) {
|
||||
dynamicBufferCount++;
|
||||
}
|
||||
desc.emplace<BufferGLES2>(dynamicOffset);
|
||||
} else {
|
||||
auto const type = GLUtils::getBufferBindingType(BufferObjectBinding::UNIFORM);
|
||||
|
||||
@@ -78,8 +78,12 @@ struct GLDescriptorSet : public HwDescriptorSet {
|
||||
private:
|
||||
// a Buffer Descriptor such as SSBO or UBO with static offset
|
||||
struct Buffer {
|
||||
Buffer() = default;
|
||||
explicit Buffer(GLenum target) noexcept : target(target) { }
|
||||
// Workaround: we cannot define the following as Buffer() = default because one of our
|
||||
// clients has their compiler set up where such declaration (possibly coupled with explicit)
|
||||
// will be considered a deleted constructor.
|
||||
Buffer() {}
|
||||
|
||||
explicit Buffer(GLenum target) noexcept : target(target) {}
|
||||
GLenum target; // 4
|
||||
GLuint id = 0; // 4
|
||||
uint32_t offset = 0; // 4
|
||||
|
||||
@@ -552,6 +552,14 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
} else if (strstr(renderer, "AMD") ||
|
||||
strstr(renderer, "ATI")) {
|
||||
// AMD/ATI GPU
|
||||
} else if (strstr(vendor, "Mesa")) {
|
||||
// Seen on
|
||||
// [Mesa],
|
||||
// [llvmpipe (LLVM 17.0.6, 256 bits)],
|
||||
// [4.5 (Core Profile) Mesa 24.0.6-1],
|
||||
// [4.50]
|
||||
// not known which version are affected
|
||||
bugs->rebind_buffer_after_deletion = true;
|
||||
} else if (strstr(renderer, "Mozilla")) {
|
||||
bugs->disable_invalidate_framebuffer = true;
|
||||
}
|
||||
@@ -929,15 +937,19 @@ void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept {
|
||||
glDeleteBuffers(n, buffers);
|
||||
void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept {
|
||||
glDeleteBuffers(1, &buffer);
|
||||
|
||||
// bindings of bound buffers are reset to 0
|
||||
const size_t targetIndex = getIndexForBufferTarget(target);
|
||||
auto& genericBuffer = state.buffers.genericBinding[targetIndex];
|
||||
UTILS_NOUNROLL
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
if (genericBuffer == buffers[i]) {
|
||||
genericBuffer = 0;
|
||||
size_t const targetIndex = getIndexForBufferTarget(target);
|
||||
auto& genericBinding = state.buffers.genericBinding[targetIndex];
|
||||
if (genericBinding == buffer) {
|
||||
genericBinding = 0;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) {
|
||||
if (genericBinding) {
|
||||
glBindBuffer(target, genericBinding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,16 +958,13 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe
|
||||
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
|
||||
|
||||
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
|
||||
auto& indexedBuffer = state.buffers.targets[targetIndex];
|
||||
UTILS_NOUNROLL // clang generates >1 KiB of code!!
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
UTILS_NOUNROLL
|
||||
for (auto& buffer : indexedBuffer.buffers) {
|
||||
if (buffer.name == buffers[i]) {
|
||||
buffer.name = 0;
|
||||
buffer.offset = 0;
|
||||
buffer.size = 0;
|
||||
}
|
||||
auto& indexedBinding = state.buffers.targets[targetIndex];
|
||||
UTILS_NOUNROLL
|
||||
for (auto& entry: indexedBinding.buffers) {
|
||||
if (entry.name == buffer) {
|
||||
entry.name = 0;
|
||||
entry.offset = 0;
|
||||
entry.size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ public:
|
||||
inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
|
||||
inline void depthRange(GLclampf near, GLclampf far) noexcept;
|
||||
|
||||
void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept;
|
||||
void deleteBuffer(GLuint buffer, GLenum target) noexcept;
|
||||
void deleteVertexArray(GLuint vao) noexcept;
|
||||
|
||||
void destroyWithContext(size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept;
|
||||
@@ -316,10 +316,15 @@ public:
|
||||
// a glFinish. So we must delay the destruction until we know the GPU is finished.
|
||||
bool delay_fbo_destruction;
|
||||
|
||||
// Mesa sometimes clears the generic buffer binding when *another* buffer is destroyed,
|
||||
// if that other buffer is bound on an *indexed* buffer binding.
|
||||
bool rebind_buffer_after_deletion;
|
||||
|
||||
// Force feature level 0. Typically used for low end ES3 devices with significant driver
|
||||
// bugs or performance issues.
|
||||
bool force_feature_level0;
|
||||
|
||||
|
||||
} bugs = {};
|
||||
|
||||
// state getters -- as needed.
|
||||
@@ -554,6 +559,9 @@ private:
|
||||
{ bugs.delay_fbo_destruction,
|
||||
"delay_fbo_destruction",
|
||||
""},
|
||||
{ bugs.rebind_buffer_after_deletion,
|
||||
"rebind_buffer_after_deletion",
|
||||
""},
|
||||
{ bugs.force_feature_level0,
|
||||
"force_feature_level0",
|
||||
""},
|
||||
|
||||
@@ -1266,6 +1266,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
|
||||
switch (t->target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
case SamplerType::SAMPLER_3D:
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
// this could be GL_TEXTURE_2D_MULTISAMPLE or GL_TEXTURE_2D_ARRAY
|
||||
@@ -1276,7 +1277,9 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
target = getCubemapTarget(binfo.layer);
|
||||
// note: cubemaps can't be multi-sampled
|
||||
break;
|
||||
default:
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
// This is an error. We have asserted in debug build.
|
||||
target = t->gl.target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1294,7 +1297,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
|
||||
if (rt->gl.samples <= 1 ||
|
||||
(rt->gl.samples > 1 && t->samples > 1 && gl.features.multisample_texture)) {
|
||||
// on GL3.2 / GLES3.1 and above multisample is handled when creating the texture.
|
||||
// On GL3.2 / GLES3.1 and above multisample is handled when creating the texture.
|
||||
// If multisampled textures are not supported and we end-up here, things should
|
||||
// still work, albeit without MSAA.
|
||||
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
|
||||
@@ -1318,6 +1321,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
GL_RENDERBUFFER, t->gl.id);
|
||||
}
|
||||
break;
|
||||
case GL_TEXTURE_3D:
|
||||
case GL_TEXTURE_2D_ARRAY:
|
||||
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
@@ -1327,7 +1331,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
if (layerCount > 1) {
|
||||
// if layerCount > 1, it means we use the multiview extension.
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, attachment,
|
||||
t->gl.id, 0, binfo.baseViewIndex, layerCount);
|
||||
t->gl.id, 0, binfo.layer, layerCount);
|
||||
} else
|
||||
#endif // !defined(__EMSCRIPTEN__) && !defined(IOS)
|
||||
{
|
||||
@@ -1431,6 +1435,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
GL_RENDERBUFFER, t->gl.id);
|
||||
}
|
||||
break;
|
||||
case GL_TEXTURE_3D:
|
||||
case GL_TEXTURE_2D_ARRAY:
|
||||
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
@@ -1727,7 +1732,7 @@ void OpenGLDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
||||
if (ibh) {
|
||||
auto& gl = mContext;
|
||||
GLIndexBuffer const* ib = handle_cast<const GLIndexBuffer*>(ibh);
|
||||
gl.deleteBuffers(1, &ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
|
||||
gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
|
||||
destruct(ibh, ib);
|
||||
}
|
||||
}
|
||||
@@ -1740,7 +1745,7 @@ void OpenGLDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
||||
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
free(bo->gl.buffer);
|
||||
} else {
|
||||
gl.deleteBuffers(1, &bo->gl.id, bo->gl.binding);
|
||||
gl.deleteBuffer(bo->gl.id, bo->gl.binding);
|
||||
}
|
||||
destruct(boh, bo);
|
||||
}
|
||||
@@ -2909,6 +2914,9 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
GLuint const fbo = gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
|
||||
|
||||
// each render-pass starts with a disabled scissor
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
|
||||
if (gl.ext.EXT_discard_framebuffer
|
||||
&& !gl.bugs.disable_invalidate_framebuffer) {
|
||||
AttachmentArray attachments; // NOLINT
|
||||
@@ -2921,7 +2929,6 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
// It's important to clear the framebuffer before drawing, as it resets
|
||||
// the fb to a known state (resets fb compression and possibly other things).
|
||||
// So we use glClear instead of glInvalidateFramebuffer
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
clearWithRasterPipe(discardFlags & ~clearFlags, { 0.0f }, 0.0f, 0);
|
||||
}
|
||||
|
||||
@@ -2937,7 +2944,6 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
}
|
||||
|
||||
if (any(clearFlags)) {
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
clearWithRasterPipe(clearFlags,
|
||||
params.clearColor, (GLfloat)params.clearDepth, (GLint)params.clearStencil);
|
||||
}
|
||||
@@ -2956,7 +2962,6 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
|
||||
#ifndef NDEBUG
|
||||
// clear the discarded (but not the cleared ones) buffers in debug builds
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
clearWithRasterPipe(discardFlags & ~clearFlags,
|
||||
{ 1, 0, 0, 1 }, 1.0, 0);
|
||||
#endif
|
||||
@@ -3462,8 +3467,8 @@ void OpenGLDriver::beginFrame(
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
||||
FrameScheduledCallback&& callback, uint64_t flags) {
|
||||
DEBUG_MARKER()
|
||||
}
|
||||
|
||||
@@ -3887,6 +3892,7 @@ void OpenGLDriver::bindDescriptorSet(
|
||||
// `offsets` data's lifetime will end when this function returns. We have to make a copy.
|
||||
// (the data is allocated inside the CommandStream)
|
||||
mBoundDescriptorSets[set].dsh = dsh;
|
||||
assert_invariant(offsets.data() != nullptr || ds->getDynamicBufferCount() == 0);
|
||||
std::copy_n(offsets.data(), ds->getDynamicBufferCount(),
|
||||
mBoundDescriptorSets[set].offsets.data());
|
||||
}
|
||||
|
||||
@@ -155,10 +155,6 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context");
|
||||
ext.egl.KHR_surfaceless_context = extensions.has("EGL_KHR_surfaceless_context");
|
||||
ext.egl.EXT_protected_content = extensions.has("EGL_EXT_protected_content");
|
||||
if (ext.egl.KHR_create_context) {
|
||||
// KHR_create_context implies KHR_surfaceless_context for ES3.x contexts
|
||||
ext.egl.KHR_surfaceless_context = true;
|
||||
}
|
||||
|
||||
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
|
||||
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
|
||||
@@ -557,6 +553,8 @@ void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
|
||||
if (swapChain) {
|
||||
SwapChainEGL const* const sc = static_cast<SwapChainEGL const*>(swapChain);
|
||||
if (sc->sur != EGL_NO_SURFACE) {
|
||||
// - if EGL_KHR_surfaceless_context is supported, mEGLDummySurface is EGL_NO_SURFACE.
|
||||
// - this is actually a bit too aggressive, but it is a rare operation.
|
||||
egl.makeCurrent(mEGLDummySurface, mEGLDummySurface);
|
||||
eglDestroySurface(mEGLDisplay, sc->sur);
|
||||
delete sc;
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
#include "ExternalStreamManagerAndroid.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
#include <utils/android/PerformanceHintManager.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
@@ -42,7 +44,9 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <new>
|
||||
#include <string_view>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -80,8 +84,6 @@ UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID =
|
||||
}
|
||||
using namespace glext;
|
||||
|
||||
using EGLStream = Platform::Stream;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
|
||||
@@ -101,38 +103,73 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept
|
||||
mExternalStreamManager(ExternalStreamManagerAndroid::create()),
|
||||
mInitializeJvmForPerformanceManagerIfNeeded(),
|
||||
mPerformanceHintManager() {
|
||||
|
||||
char scratch[PROP_VALUE_MAX + 1];
|
||||
int length = __system_property_get("ro.build.version.release", scratch);
|
||||
int const androidVersion = length >= 0 ? atoi(scratch) : 1;
|
||||
if (!androidVersion) {
|
||||
mOSVersion = 1000; // if androidVersion is 0, it means "future"
|
||||
} else {
|
||||
length = __system_property_get("ro.build.version.sdk", scratch);
|
||||
mOSVersion = length >= 0 ? atoi(scratch) : 1;
|
||||
mOSVersion = android_get_device_api_level();
|
||||
if (mOSVersion < 0) {
|
||||
mOSVersion = __ANDROID_API_FUTURE__;
|
||||
}
|
||||
|
||||
// This disables an ANGLE optimization on ARM, which turns out to be more costly for us
|
||||
// see b/229017581
|
||||
// We need to do this before we create the GL context.
|
||||
// An alternative solution is use a system property:
|
||||
// __system_property_set(
|
||||
// "debug.angle.feature_overrides_disabled",
|
||||
// "preferSubmitAtFBOBoundary");
|
||||
// but that would outlive this process, so the environment variable is better.
|
||||
// We also make sure to not update the variable if it already exists.
|
||||
// There is no harm setting this if we're not on ANGLE or ARM.
|
||||
setenv("ANGLE_FEATURE_OVERRIDES_DISABLED", "preferSubmitAtFBOBoundary", false);
|
||||
mNativeWindowLib = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
|
||||
if (mNativeWindowLib) {
|
||||
ANativeWindow_getBuffersDefaultDataSpace =
|
||||
(int32_t(*)(ANativeWindow*))dlsym(mNativeWindowLib,
|
||||
"ANativeWindow_getBuffersDefaultDataSpace");
|
||||
}
|
||||
}
|
||||
|
||||
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
|
||||
|
||||
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
|
||||
if (mNativeWindowLib) {
|
||||
dlclose(mNativeWindowLib);
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::terminate() noexcept {
|
||||
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
|
||||
PlatformEGL::terminate();
|
||||
}
|
||||
|
||||
static constexpr const std::string_view kNativeWindowInvalidMsg =
|
||||
"ANativeWindow is invalid. It probably has been destroyed. EGL surface = ";
|
||||
|
||||
bool PlatformEGLAndroid::makeCurrent(ContextType type,
|
||||
SwapChain* drawSwapChain,
|
||||
SwapChain* readSwapChain) noexcept {
|
||||
|
||||
// fast & safe path
|
||||
if (UTILS_LIKELY(!mAssertNativeWindowIsValid)) {
|
||||
return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain);
|
||||
}
|
||||
|
||||
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
|
||||
if (ANativeWindow_getBuffersDefaultDataSpace) {
|
||||
// anw can be nullptr if we're using a pbuffer surface
|
||||
if (UTILS_LIKELY(dsc->nativeWindow)) {
|
||||
// this a proxy of is_valid()
|
||||
auto result = ANativeWindow_getBuffersDefaultDataSpace(dsc->nativeWindow);
|
||||
FILAMENT_CHECK_POSTCONDITION(result >= 0) << kNativeWindowInvalidMsg << dsc->sur;
|
||||
}
|
||||
} else {
|
||||
// If we don't have ANativeWindow_getBuffersDefaultDataSpace, we revert to using the
|
||||
// private query() call.
|
||||
// Shadow version if the real ANativeWindow, so we can access the query() hook. Query
|
||||
// has existed since forever, probably Android 1.0.
|
||||
struct NativeWindow {
|
||||
// is valid query enum value
|
||||
enum { IS_VALID = 17 };
|
||||
uint64_t pad[18];
|
||||
int (* query)(ANativeWindow const*, int, int*);
|
||||
} const* pWindow = reinterpret_cast<NativeWindow const*>(dsc->nativeWindow);
|
||||
int isValid = 0;
|
||||
if (UTILS_LIKELY(pWindow->query)) { // just in case it's nullptr
|
||||
int const err = pWindow->query(dsc->nativeWindow, NativeWindow::IS_VALID, &isValid);
|
||||
if (UTILS_LIKELY(err >= 0)) { // in case the IS_VALID enum is not recognized
|
||||
// query call succeeded
|
||||
FILAMENT_CHECK_POSTCONDITION(isValid) << kNativeWindowInvalidMsg << dsc->sur;
|
||||
}
|
||||
}
|
||||
}
|
||||
return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain);
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::beginFrame(
|
||||
int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs,
|
||||
@@ -189,6 +226,8 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
"eglGetFrameTimestampsANDROID");
|
||||
}
|
||||
|
||||
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 <backend/platforms/PlatformOSMesa.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace backend;
|
||||
|
||||
namespace {
|
||||
|
||||
using BackingType = GLfloat;
|
||||
#define BACKING_GL_TYPE GL_FLOAT
|
||||
|
||||
struct OSMesaSwapchain {
|
||||
OSMesaSwapchain(uint32_t width, uint32_t height)
|
||||
: width(width),
|
||||
height(height),
|
||||
buffer(new uint8_t[width * height * 4 * sizeof(BackingType)]) {}
|
||||
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
std::unique_ptr<uint8_t[]> buffer;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
|
||||
const DriverConfig& driverConfig) noexcept {
|
||||
FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr)
|
||||
<< "shared GL context is not supported with PlatformOSMesa";
|
||||
mContext = 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.
|
||||
auto chain = createSwapChain(1, 1, 0);
|
||||
makeCurrent(ContextType::UNPROTECTED, chain, nullptr);
|
||||
destroySwapChain(chain);
|
||||
|
||||
int result = bluegl::bind();
|
||||
FILAMENT_CHECK_POSTCONDITION(!result) << "Unable to load OpenGL entry points.";
|
||||
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
|
||||
}
|
||||
|
||||
void PlatformOSMesa::terminate() noexcept {
|
||||
OSMesaDestroyContext(mContext);
|
||||
bluegl::unbind();
|
||||
}
|
||||
|
||||
Platform::SwapChain* PlatformOSMesa::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Cannot create non-headless swapchain";
|
||||
return (SwapChain*) nativeWindow;
|
||||
}
|
||||
|
||||
Platform::SwapChain* PlatformOSMesa::createSwapChain(uint32_t width, uint32_t height,
|
||||
uint64_t flags) noexcept {
|
||||
OSMesaSwapchain* swapchain = new OSMesaSwapchain(width, height);
|
||||
return (SwapChain*) swapchain;
|
||||
}
|
||||
|
||||
void PlatformOSMesa::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
|
||||
OSMesaSwapchain* impl = (OSMesaSwapchain*) swapChain;
|
||||
delete impl;
|
||||
}
|
||||
|
||||
bool PlatformOSMesa::makeCurrent(ContextType type, SwapChain* drawSwapChain,
|
||||
SwapChain* readSwapChain) noexcept {
|
||||
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!";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformOSMesa::commit(Platform::SwapChain* swapChain) noexcept {
|
||||
// No-op since we are not scanning out to a display.
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +410,6 @@ io::ostream& operator<<(io::ostream& out, const RasterState& rs) {
|
||||
io::ostream& operator<<(io::ostream& out, const TargetBufferInfo& tbi) {
|
||||
return out << "TargetBufferInfo{"
|
||||
<< "handle=" << tbi.handle
|
||||
<< ", baseViewIndex=" << tbi.baseViewIndex
|
||||
<< ", level=" << tbi.level
|
||||
<< ", layer=" << tbi.layer << "}";
|
||||
}
|
||||
|
||||
@@ -66,10 +66,10 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V
|
||||
1, blitRegions, filter);
|
||||
|
||||
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
|
||||
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
|
||||
oldSrcLayout = src.texture->getDefaultLayout();
|
||||
}
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
|
||||
oldDstLayout = dst.texture->getDefaultLayout();
|
||||
}
|
||||
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
|
||||
@@ -109,10 +109,10 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect
|
||||
1, resolveRegions);
|
||||
|
||||
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
|
||||
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
|
||||
oldSrcLayout = src.texture->getDefaultLayout();
|
||||
}
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
|
||||
oldDstLayout = dst.texture->getDefaultLayout();
|
||||
}
|
||||
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
|
||||
@@ -151,7 +151,8 @@ void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
|
||||
}
|
||||
#endif
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VulkanCommandBuffer& commands = dst.texture->isProtected() ?
|
||||
mCommands->getProtected() : mCommands->get();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
resolveFast(&commands, aspect, src, dst);
|
||||
@@ -176,7 +177,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->isProtected() ?
|
||||
mCommands->getProtected() : mCommands->get();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(&commands, aspect, filter, src, dst, srcRectPair, dstRectPair);
|
||||
|
||||
@@ -33,48 +33,28 @@ 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 {
|
||||
@@ -130,178 +110,147 @@ bool VulkanGroupMarkers::empty() const noexcept {
|
||||
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VulkanContext* context, VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext* context, VulkanResourceAllocator* allocator,
|
||||
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) {
|
||||
mResourceManager(allocator),
|
||||
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);
|
||||
mIsProtected = isProtected;
|
||||
}
|
||||
|
||||
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;
|
||||
mResourceManager.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 {
|
||||
FVK_LOGE <<"[" << (mIsProtected ? "protected" : "regular") <<"]=" << buffer() << " begin"
|
||||
<<utils::io::endl;
|
||||
// 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];
|
||||
FVK_LOGE <<"[" << (mIsProtected ? "protected" : "regular") <<"]=" << buffer() << " end"
|
||||
<<utils::io::endl;
|
||||
|
||||
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);
|
||||
VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, mFence);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
if (result != VK_SUCCESS) {
|
||||
@@ -309,86 +258,212 @@ bool VulkanCommands::flush() {
|
||||
}
|
||||
#endif
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
mWaitSemaphores.clear();
|
||||
return mSubmission;
|
||||
}
|
||||
|
||||
mSubmissionSignal = renderingFinished;
|
||||
mInjectedSignal = VK_NULL_HANDLE;
|
||||
mCurrentCommandBufferIndex = -1;
|
||||
CommandBufferPool::CommandBufferPool(VulkanContext* context, VulkanResourceAllocator* allocator,
|
||||
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, allocator, 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();
|
||||
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);
|
||||
}
|
||||
|
||||
void CommandBufferPool::pushMarker(char const* marker) {
|
||||
getRecording().pushMarker(marker);
|
||||
}
|
||||
|
||||
void CommandBufferPool::popMarker() {
|
||||
getRecording().popMarker();
|
||||
}
|
||||
|
||||
void CommandBufferPool::insertEvent(char const* marker) {
|
||||
getRecording().insertEvent(marker);
|
||||
}
|
||||
|
||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context,
|
||||
VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
mProtectedQueue(protectedQueue),
|
||||
mProtectedQueueFamilyIndex(protectedQueueFamilyIndex),
|
||||
mAllocator(allocator),
|
||||
mContext(context),
|
||||
mPool(std::make_unique<CommandBufferPool>(context, allocator, 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, mAllocator, mDevice,
|
||||
mProtectedQueue, mProtectedQueueFamilyIndex, true);
|
||||
}
|
||||
auto& ret = mProtectedPool->getRecording();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VulkanCommands::flush() {
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,29 +477,14 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
|
||||
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);
|
||||
mPool->pushMarker(str);
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->pushMarker(str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,10 +502,9 @@ void VulkanCommands::popGroupMarker() {
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
#endif
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
vkCmdDebugMarkerEndEXT(cmdbuffer);
|
||||
mPool->popMarker();
|
||||
if (mProtectedPool) {
|
||||
mProtectedPool->popMarker();
|
||||
}
|
||||
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||
// It could be that pop is called between flush() and get() (new command buffer), in which
|
||||
@@ -455,22 +514,10 @@ void VulkanCommands::popGroupMarker() {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <utils/Condition.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
@@ -61,43 +62,20 @@ private:
|
||||
|
||||
// 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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
void setStatus(VkResult value) { status.store(value); }
|
||||
|
||||
VkFence& getFence() {
|
||||
return fence;
|
||||
}
|
||||
|
||||
VkResult getStatus() {
|
||||
std::unique_lock<utils::Mutex> lock(mutex);
|
||||
return status.load(std::memory_order_acquire);
|
||||
}
|
||||
VkResult getStatus() { return status.load(std::memory_order_acquire); }
|
||||
|
||||
private:
|
||||
VkFence fence;
|
||||
utils::Condition condition;
|
||||
utils::Mutex mutex;
|
||||
std::atomic<VkResult> status;
|
||||
};
|
||||
|
||||
@@ -105,11 +83,14 @@ private:
|
||||
// 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, VulkanResourceAllocator* allocator,
|
||||
VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected);
|
||||
|
||||
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
|
||||
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
|
||||
|
||||
~VulkanCommandBuffer();
|
||||
|
||||
inline void acquire(VulkanResource* resource) {
|
||||
mResourceManager.acquire(resource);
|
||||
}
|
||||
@@ -117,41 +98,89 @@ struct VulkanCommandBuffer {
|
||||
inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) {
|
||||
mResourceManager.acquireAll(srcResources);
|
||||
}
|
||||
void reset() noexcept;
|
||||
|
||||
inline void reset() {
|
||||
fence.reset();
|
||||
mResourceManager.clear();
|
||||
mPipeline = VK_NULL_HANDLE;
|
||||
inline void insertWait(VkSemaphore sem) {
|
||||
mWaitSemaphores.insert(sem);
|
||||
}
|
||||
|
||||
inline void setPipeline(VkPipeline pipeline) {
|
||||
mPipeline = pipeline;
|
||||
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 VkPipeline pipeline() const {
|
||||
return mPipeline;
|
||||
VkResult getStatus() {
|
||||
return mFenceStatus->getStatus();
|
||||
}
|
||||
|
||||
inline VkCommandBuffer buffer() const {
|
||||
if (fence) {
|
||||
return mBuffer;
|
||||
}
|
||||
return VK_NULL_HANDLE;
|
||||
std::shared_ptr<VulkanCmdFence> getFenceStatus() const {
|
||||
return mFenceStatus;
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
VkFence getVkFence() const {
|
||||
return mFence;
|
||||
}
|
||||
|
||||
VkCommandBuffer buffer() const {
|
||||
FVK_LOGE <<"-- getting " << mBuffer << " -- ";
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
// debug only
|
||||
bool mIsProtected;
|
||||
|
||||
private:
|
||||
VulkanContext* mContext;
|
||||
uint8_t mMarkerCount;
|
||||
bool const isProtected;
|
||||
VkDevice mDevice;
|
||||
VkQueue mQueue;
|
||||
VulkanAcquireOnlyResourceManager mResourceManager;
|
||||
CappedArray<VkSemaphore, 2> mWaitSemaphores;
|
||||
VkCommandBuffer mBuffer;
|
||||
VkPipeline mPipeline;
|
||||
VkSemaphore mSubmission;
|
||||
VkFence mFence;
|
||||
std::shared_ptr<VulkanCmdFence> mFenceStatus;
|
||||
|
||||
};
|
||||
|
||||
// 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::bitset32;
|
||||
static constexpr int8_t INVALID = -1;
|
||||
|
||||
CommandBufferPool(VulkanContext* context, VulkanResourceAllocator* allocator, VkDevice device,
|
||||
VkQueue queue, uint8_t queueFamilyIndex, bool isProtected);
|
||||
~CommandBufferPool();
|
||||
|
||||
VulkanCommandBuffer& getRecording();
|
||||
|
||||
void gc();
|
||||
void update();
|
||||
VkSemaphore flush();
|
||||
void wait();
|
||||
|
||||
void waitFor(VkSemaphore previousAction);
|
||||
|
||||
void pushMarker(char const* marker);
|
||||
void popMarker();
|
||||
void insertEvent(char const* marker);
|
||||
|
||||
inline bool isRecording() const { return mRecording != INVALID; }
|
||||
|
||||
private:
|
||||
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
|
||||
using BufferList = utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>>;
|
||||
VkDevice mDevice;
|
||||
VkCommandPool mPool;
|
||||
ActiveBuffers mSubmitted;
|
||||
std::vector<std::unique_ptr<VulkanCommandBuffer>> mBuffers;
|
||||
int8_t mRecording;
|
||||
};
|
||||
|
||||
// Manages a set of command buffers and semaphores, exposing an API that is significantly simpler
|
||||
@@ -165,9 +194,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 +209,18 @@ public:
|
||||
class VulkanCommands {
|
||||
public:
|
||||
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VulkanContext* context, VulkanResourceAllocator* allocator);
|
||||
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context,
|
||||
VulkanResourceAllocator* allocator);
|
||||
|
||||
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 +228,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,37 +249,31 @@ 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;
|
||||
VulkanResourceAllocator* mAllocator;
|
||||
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;
|
||||
|
||||
VkSemaphore mInjectedDependency = VK_NULL_HANDLE;
|
||||
VkSemaphore mLastSubmit = VK_NULL_HANDLE;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG)
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG | FVK_DEBUG_VALIDATION | FVK_DEBUG_DUMP_API)
|
||||
#else
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#endif
|
||||
|
||||
@@ -121,7 +121,7 @@ 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,
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
VK_DEFINE_HANDLE(VmaAllocator)
|
||||
VK_DEFINE_HANDLE(VmaPool)
|
||||
|
||||
@@ -43,7 +46,6 @@ struct VulkanCommandBuffer;
|
||||
struct VulkanAttachment {
|
||||
VulkanTexture* texture = nullptr;
|
||||
uint8_t level = 0;
|
||||
uint8_t baseViewIndex = 0;
|
||||
uint8_t layerCount = 1;
|
||||
uint16_t layer = 0;
|
||||
|
||||
@@ -83,24 +85,53 @@ private:
|
||||
};
|
||||
|
||||
struct VulkanRenderPass {
|
||||
VulkanCommandBuffer* cmdBuffer;
|
||||
VulkanRenderTarget* renderTarget;
|
||||
VkRenderPass renderPass;
|
||||
RenderPassParams params;
|
||||
int currentSubpass;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
template< typename T >
|
||||
std::string int_to_hex( T i )
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << "0x"
|
||||
<< std::setfill ('0') << std::setw(sizeof(T)*2)
|
||||
<< std::hex << i;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
bool printed = false;
|
||||
|
||||
}
|
||||
|
||||
// This is a collection of immutable data about the vulkan context. This actual handles to the
|
||||
// context are stored in VulkanPlatform.
|
||||
struct VulkanContext {
|
||||
public:
|
||||
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
|
||||
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
||||
if (flags & 1) {
|
||||
inline uint32_t selectMemoryType(uint32_t flags, VkMemoryPropertyFlags reqs) const {
|
||||
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
|
||||
assert_invariant(isProtectedMemorySupported()==true);
|
||||
}
|
||||
|
||||
if (!printed) {
|
||||
for (uint32_t i = 0; i < mMemoryProperties.memoryTypeCount; i++) {
|
||||
utils::slog.e << "[" << int_to_hex((uint8_t) i) << "]="
|
||||
<< int_to_hex(
|
||||
(uint32_t) mMemoryProperties.memoryTypes[i].propertyFlags)
|
||||
<< utils::io::endl;
|
||||
}
|
||||
printed = true;
|
||||
}
|
||||
for (uint32_t i = 0; i < mMemoryProperties.memoryTypeCount; i++) {
|
||||
if (flags & (1ULL << i)) {
|
||||
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
flags >>= 1;
|
||||
}
|
||||
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
||||
}
|
||||
@@ -114,19 +145,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 {
|
||||
@@ -142,16 +173,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;
|
||||
|
||||
@@ -88,28 +88,6 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
|
||||
return allocator;
|
||||
}
|
||||
|
||||
VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) {
|
||||
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
||||
commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
|
||||
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
|
||||
stagePool, true /* heap allocated */);
|
||||
uint32_t black = 0;
|
||||
PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
|
||||
emptyTexture->updateImage(pbd, 1, 1, 1, 0, 0, 0, 0);
|
||||
return emptyTexture;
|
||||
}
|
||||
|
||||
VulkanBufferObject* createEmptyBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
VulkanCommands* commands) {
|
||||
VulkanBufferObject* obj =
|
||||
new VulkanBufferObject(allocator, stagePool, 1, BufferObjectBinding::UNIFORM);
|
||||
uint8_t byte = 0;
|
||||
obj->buffer.loadFromCpu(commands->get().buffer(), &byte, 0, 1);
|
||||
return obj;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
|
||||
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
|
||||
@@ -220,8 +198,9 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mResourceAllocator(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck),
|
||||
mResourceManager(&mResourceAllocator),
|
||||
mThreadSafeResourceManager(&mResourceAllocator),
|
||||
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
|
||||
mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator),
|
||||
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(),
|
||||
mPlatform->getProtectedGraphicsQueue(), mPlatform->getProtectedGraphicsQueueFamilyIndex(),
|
||||
&mContext, &mResourceAllocator),
|
||||
mPipelineLayoutCache(mPlatform->getDevice(), &mResourceAllocator),
|
||||
mPipelineCache(mPlatform->getDevice(), mAllocator),
|
||||
mStagePool(mAllocator, &mCommands),
|
||||
@@ -255,13 +234,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
#endif
|
||||
|
||||
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
|
||||
|
||||
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
|
||||
mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool);
|
||||
mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands);
|
||||
|
||||
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
|
||||
mEmptyBufferObject);
|
||||
}
|
||||
|
||||
VulkanDriver::~VulkanDriver() noexcept = default;
|
||||
@@ -326,9 +298,6 @@ void VulkanDriver::terminate() {
|
||||
// to those commands are no longer referenced.
|
||||
finish(0);
|
||||
|
||||
delete mEmptyBufferObject;
|
||||
delete mEmptyTexture;
|
||||
|
||||
// Command buffers should come first since it might have commands depending on resources that
|
||||
// are about to be destroyed.
|
||||
mCommands.terminate();
|
||||
@@ -397,10 +366,11 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
FVK_SYSTRACE_START("beginFrame");
|
||||
// Do nothing.
|
||||
FVK_SYSTRACE_END();
|
||||
FVK_LOGE <<"-----begin frame ---" << utils::io::endl;
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
||||
FrameScheduledCallback&& callback, uint64_t flags) {
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
@@ -416,6 +386,7 @@ void VulkanDriver::endFrame(uint32_t frameId) {
|
||||
mCommands.flush();
|
||||
collectGarbage();
|
||||
FVK_SYSTRACE_END();
|
||||
FVK_LOGE <<"-----end frame ---" << utils::io::endl;
|
||||
}
|
||||
|
||||
void VulkanDriver::updateDescriptorSetBuffer(
|
||||
@@ -424,9 +395,13 @@ void VulkanDriver::updateDescriptorSetBuffer(
|
||||
backend::BufferObjectHandle boh,
|
||||
uint32_t offset,
|
||||
uint32_t size) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("updateDescriptorSetBuffer");
|
||||
|
||||
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
|
||||
VulkanBufferObject* obj = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
mDescriptorSetManager.updateBuffer(set, binding, obj, offset, size);
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::updateDescriptorSetTexture(
|
||||
@@ -434,18 +409,13 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
backend::descriptor_binding_t binding,
|
||||
backend::TextureHandle th,
|
||||
SamplerParams params) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("updateDescriptorSetTexture");
|
||||
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
|
||||
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(th);
|
||||
|
||||
// We need to make sure the initial layout transition has been completed before we can write
|
||||
// the sampler descriptor. We flush and wait until the transition has been completed.
|
||||
if (!texture->transitionReady()) {
|
||||
mCommands.flush();
|
||||
mCommands.wait();
|
||||
}
|
||||
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(params);
|
||||
mDescriptorSetManager.updateSampler(set, binding, texture, vksampler);
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::flush(int) {
|
||||
@@ -547,25 +517,17 @@ void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
||||
void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||
TextureUsage usage) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("createTexture");
|
||||
|
||||
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
|
||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
|
||||
target, levels,
|
||||
format, samples, w, h, depth, usage, mStagePool);
|
||||
mResourceManager.acquire(vktexture);
|
||||
}
|
||||
|
||||
//void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||
// TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||
// TextureUsage usage,
|
||||
// TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
|
||||
// TextureSwizzle swizzleArray[] = {r, g, b, a};
|
||||
// const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
|
||||
// auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
|
||||
// mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
|
||||
// target, levels, format, samples, w, h, depth, usage, mStagePool,
|
||||
// false /*heap allocated */, swizzleMap);
|
||||
// mResourceManager.acquire(vktexture);
|
||||
//}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
|
||||
uint8_t baseLevel, uint8_t levelCount) {
|
||||
@@ -614,9 +576,12 @@ void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
}
|
||||
|
||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("createProgram");
|
||||
auto vkprogram
|
||||
= mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||
mResourceManager.acquire(vkprogram);
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
@@ -637,6 +602,9 @@ void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
||||
void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
TargetBufferFlags targets, uint32_t width, uint32_t height, uint8_t samples,
|
||||
uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("createRenderTarget");
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmin = {std::numeric_limits<uint32_t>::max()};
|
||||
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmax = {0};
|
||||
UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0;
|
||||
@@ -647,7 +615,6 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
colorTargets[i] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
|
||||
.level = color[i].level,
|
||||
.baseViewIndex = color[i].baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = color[i].layer,
|
||||
};
|
||||
@@ -663,7 +630,6 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
depthStencil[0] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
|
||||
.level = depth.level,
|
||||
.baseViewIndex = depth.baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = depth.layer,
|
||||
};
|
||||
@@ -677,7 +643,6 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
depthStencil[1] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
|
||||
.level = stencil.level,
|
||||
.baseViewIndex = stencil.baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = stencil.layer,
|
||||
};
|
||||
@@ -698,6 +663,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
&mCommands, &mResourceAllocator, width, height, samples, colorTargets, depthStencil,
|
||||
mStagePool, layerCount);
|
||||
mResourceManager.acquire(renderTarget);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
@@ -713,8 +680,13 @@ void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
}
|
||||
|
||||
void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||
VulkanCommandBuffer const& commandBuffer = mCommands.get();
|
||||
mResourceAllocator.construct<VulkanFence>(fh, commandBuffer.fence);
|
||||
VulkanCommandBuffer* cmdbuf = nullptr;
|
||||
if (mCurrentRenderPass.cmdBuffer) {
|
||||
cmdbuf = mCurrentRenderPass.cmdBuffer;
|
||||
} else {
|
||||
cmdbuf = &mCommands.get();
|
||||
}
|
||||
mResourceAllocator.construct<VulkanFence>(fh, cmdbuf->getFenceStatus());
|
||||
}
|
||||
|
||||
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
@@ -726,6 +698,15 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
|
||||
mAllocator, &mCommands, &mResourceAllocator, mStagePool, nativeWindow, flags);
|
||||
mResourceManager.acquire(swapChain);
|
||||
|
||||
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
|
||||
if (!isProtectedContentSupported()) {
|
||||
utils::slog.w << "protected swapchain requested, but Vulkan does not support it"
|
||||
<< utils::io::endl;
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
|
||||
@@ -749,17 +730,25 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
VulkanDescriptorSetLayout* layout = mResourceAllocator.construct<VulkanDescriptorSetLayout>(
|
||||
dslh, mPlatform->getDevice(), info);
|
||||
dslh, info);
|
||||
|
||||
// This will create a VkDescriptorSetLayout (which is cached) for this object.
|
||||
mDescriptorSetManager.initVkLayout(layout);
|
||||
mResourceManager.acquire(layout);
|
||||
}
|
||||
|
||||
void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
Handle<HwDescriptorSetLayout> dslh) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("createDescriptorSet");
|
||||
|
||||
auto layout = mResourceAllocator.handle_cast<VulkanDescriptorSetLayout*>(dslh);
|
||||
mDescriptorSetManager.createSet(dsh, layout);
|
||||
|
||||
auto set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
|
||||
mResourceManager.acquire(set);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
|
||||
@@ -992,8 +981,7 @@ bool VulkanDriver::isSRGBSwapChainSupported() {
|
||||
}
|
||||
|
||||
bool VulkanDriver::isProtectedContentSupported() {
|
||||
// the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported
|
||||
return false;
|
||||
return mContext.isProtectedMemorySupported();
|
||||
}
|
||||
|
||||
bool VulkanDriver::isStereoSupported() {
|
||||
@@ -1257,14 +1245,17 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("beginRenderPass");
|
||||
|
||||
FVK_LOGE <<"begin render pass" << utils::io::endl;
|
||||
|
||||
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
|
||||
VkExtent2D const extent = rt->getExtent();
|
||||
VkExtent2D const& extent = rt->getExtent();
|
||||
assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0);
|
||||
|
||||
// Filament has the expectation that the contents of the swap chain are not preserved on the
|
||||
// first render pass. Note however that its contents are often preserved on subsequent render
|
||||
// passes, due to multiple views.
|
||||
TargetBufferFlags discardStart = params.flags.discardStart;
|
||||
|
||||
if (rt->isSwapChain()) {
|
||||
VulkanSwapChain* sc = mCurrentSwapChain;
|
||||
assert_invariant(sc);
|
||||
@@ -1274,10 +1265,9 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
}
|
||||
|
||||
VulkanAttachment depth = rt->getSamples() == 1 ? rt->getDepth() : rt->getMsaaDepth();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
if (depth.texture) {
|
||||
if (rt->hasDepth()) {
|
||||
auto depth = rt->getDepth();
|
||||
depth.texture->print();
|
||||
}
|
||||
#endif
|
||||
@@ -1286,121 +1276,44 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
// If that's the case, we need to change the layout of the texture to DEPTH_SAMPLER, which is a
|
||||
// more general layout. Otherwise, we prefer the DEPTH_ATTACHMENT layout, which is optimal for
|
||||
// the non-sampling case.
|
||||
VulkanCommandBuffer& commands = mCommands.get();
|
||||
VulkanCommandBuffer& commands = rt->isProtected() ? mCommands.getProtected() : mCommands.get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
VulkanLayout currentDepthLayout = depth.getLayout();
|
||||
|
||||
// Scissor is reset with each render pass
|
||||
// This also takes care of VUID-vkCmdDrawIndexed-None-07832.
|
||||
VkRect2D const scissor{ .offset = { 0, 0 }, .extent = extent };
|
||||
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
|
||||
|
||||
VulkanLayout currentDepthLayout = VulkanLayout::UNDEFINED;
|
||||
TargetBufferFlags clearVal = params.flags.clear;
|
||||
TargetBufferFlags discardEndVal = params.flags.discardEnd;
|
||||
if (depth.texture) {
|
||||
if (rt->hasDepth()) {
|
||||
if (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) {
|
||||
discardEndVal &= ~TargetBufferFlags::DEPTH;
|
||||
clearVal &= ~TargetBufferFlags::DEPTH;
|
||||
}
|
||||
// If the depth attachment texture was previously sampled, then we need to manually
|
||||
// transition it to an attachment. This is necessary to also set up a barrier between the
|
||||
// previous read and the potentially coming write.
|
||||
depth.texture->transitionLayout(&commands, depth.getSubresourceRange(),
|
||||
VulkanLayout::DEPTH_ATTACHMENT);
|
||||
currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
}
|
||||
|
||||
uint8_t const renderTargetLayerCount = rt->getLayerCount();
|
||||
|
||||
// Create the VkRenderPass or fetch it from cache.
|
||||
VulkanFboCache::RenderPassKey rpkey = {
|
||||
.initialColorLayoutMask = 0,
|
||||
.initialDepthLayout = currentDepthLayout,
|
||||
.depthFormat = depth.getFormat(),
|
||||
.clear = clearVal,
|
||||
.discardStart = discardStart,
|
||||
.discardEnd = discardEndVal,
|
||||
.samples = rt->getSamples(),
|
||||
.subpassMask = uint8_t(params.subpassMask),
|
||||
.viewCount = renderTargetLayerCount,
|
||||
};
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
VulkanAttachment const& info = rt->getColor(i);
|
||||
if (info.texture) {
|
||||
assert_invariant(info.layerCount == renderTargetLayerCount);
|
||||
rpkey.initialColorLayoutMask |= 1 << i;
|
||||
rpkey.colorFormat[i] = info.getFormat();
|
||||
if (rpkey.samples > 1 && info.texture->samples == 1) {
|
||||
rpkey.needsResolveMask |= (1 << i);
|
||||
}
|
||||
} else {
|
||||
rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
VulkanFboCache::RenderPassKey rpkey = rt->getRenderPassKey();
|
||||
rpkey.clear = clearVal;
|
||||
rpkey.discardStart = discardStart;
|
||||
rpkey.discardEnd = discardEndVal;
|
||||
rpkey.initialDepthLayout = currentDepthLayout;
|
||||
rpkey.subpassMask = uint8_t(params.subpassMask);
|
||||
|
||||
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
||||
mPipelineCache.bindRenderPass(renderPass, 0);
|
||||
|
||||
// Create the VkFramebuffer or fetch it from cache.
|
||||
VulkanFboCache::FboKey fbkey {
|
||||
.renderPass = renderPass,
|
||||
.width = (uint16_t) extent.width,
|
||||
.height = (uint16_t) extent.height,
|
||||
.layers = 1,
|
||||
.samples = rpkey.samples,
|
||||
};
|
||||
auto& renderPassAttachments = mRenderPassFboInfo.attachments;
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
VulkanAttachment& attachment = rt->getColor(i);
|
||||
if (!attachment.texture) {
|
||||
fbkey.color[i] = VK_NULL_HANDLE;
|
||||
fbkey.resolve[i] = VK_NULL_HANDLE;
|
||||
continue;
|
||||
}
|
||||
VulkanFboCache::FboKey fbkey = rt->getFboKey();
|
||||
fbkey.renderPass = renderPass;
|
||||
fbkey.layers = 1;
|
||||
|
||||
if (fbkey.samples == 1) {
|
||||
auto const& range = attachment.getSubresourceRange();
|
||||
attachment.texture->transitionLayout(&commands,
|
||||
range, VulkanLayout::COLOR_ATTACHMENT);
|
||||
renderPassAttachments.insert(attachment);
|
||||
fbkey.color[i] = attachment.getImageView();
|
||||
fbkey.resolve[i] = VK_NULL_HANDLE;
|
||||
assert_invariant(fbkey.color[i]);
|
||||
} else {
|
||||
auto& msaaColorAttachment = rt->getMsaaColor(i);
|
||||
auto const& msaaRange = attachment.getSubresourceRange();
|
||||
msaaColorAttachment.texture->transitionLayout(&commands,
|
||||
msaaRange, VulkanLayout::COLOR_ATTACHMENT);
|
||||
renderPassAttachments.insert(msaaColorAttachment);
|
||||
rt->emitBarriersBeginRenderPass(commands);
|
||||
|
||||
fbkey.color[i] = msaaColorAttachment.getImageView();
|
||||
|
||||
VulkanTexture* texture = attachment.texture;
|
||||
if (texture->samples == 1) {
|
||||
mRenderPassFboInfo.hasColorResolve = true;
|
||||
|
||||
auto const& range = attachment.getSubresourceRange();
|
||||
attachment.texture->transitionLayout(&commands,
|
||||
range, VulkanLayout::COLOR_ATTACHMENT);
|
||||
renderPassAttachments.insert(attachment);
|
||||
fbkey.resolve[i] = attachment.getImageView();
|
||||
assert_invariant(fbkey.resolve[i]);
|
||||
}
|
||||
assert_invariant(fbkey.color[i]);
|
||||
}
|
||||
}
|
||||
if (depth.texture) {
|
||||
fbkey.depth = depth.getImageView();
|
||||
assert_invariant(fbkey.depth);
|
||||
renderPassAttachments.insert(depth);
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE bool const depthDiscardEnd =
|
||||
any(rpkey.discardEnd & TargetBufferFlags::DEPTH);
|
||||
|
||||
// Vulkan 1.1 does not support multisampled depth resolve, so let's check here
|
||||
// and assert if this is requested. (c.f. isAutoDepthResolveSupported)
|
||||
// Reminder: Filament's backend API works like this:
|
||||
// - If the RT is SS then all attachments must be SS.
|
||||
// - If the RT is MS then all SS attachments are auto resolved if not discarded.
|
||||
assert_invariant(!(rt->getSamples() > 1 &&
|
||||
rt->getDepth().texture->samples == 1 &&
|
||||
!depthDiscardEnd));
|
||||
}
|
||||
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
|
||||
// Assign a label to the framebuffer for debugging purposes.
|
||||
@@ -1414,9 +1327,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
|
||||
// The current command buffer now has references to the render target and its attachments.
|
||||
commands.acquire(rt);
|
||||
for (auto const& attachment: renderPassAttachments) {
|
||||
commands.acquire(attachment.texture);
|
||||
}
|
||||
|
||||
// Populate the structures required for vkCmdBeginRenderPass.
|
||||
VkRenderPassBeginInfo renderPassInfo {
|
||||
@@ -1435,7 +1345,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT +
|
||||
1] = {};
|
||||
if (clearVal != TargetBufferFlags::NONE) {
|
||||
|
||||
// NOTE: clearValues must be populated in the same order as the attachments array in
|
||||
// VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
|
||||
// actually clearing that particular target.
|
||||
@@ -1472,10 +1381,11 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
.maxDepth = params.depthRange.far
|
||||
};
|
||||
|
||||
rt->transformClientRectToPlatform(&viewport);
|
||||
rt->transformViewportToPlatform(&viewport);
|
||||
vkCmdSetViewport(cmdbuffer, 0, 1, &viewport);
|
||||
|
||||
mCurrentRenderPass = {
|
||||
.cmdBuffer = &commands,
|
||||
.renderTarget = rt,
|
||||
.renderPass = renderPassInfo.renderPass,
|
||||
.params = params,
|
||||
@@ -1488,8 +1398,10 @@ void VulkanDriver::endRenderPass(int) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("endRenderPass");
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands.get();
|
||||
VkCommandBuffer cmdbuffer = commands.buffer();
|
||||
FVK_LOGE <<"end render pass" << utils::io::endl;
|
||||
|
||||
VulkanCommandBuffer* cmdbuf = mCurrentRenderPass.cmdBuffer;
|
||||
VkCommandBuffer cmdbuffer = cmdbuf->buffer();
|
||||
vkCmdEndRenderPass(cmdbuffer);
|
||||
|
||||
VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
@@ -1497,26 +1409,9 @@ void VulkanDriver::endRenderPass(int) {
|
||||
|
||||
// Since we might soon be sampling from the render target that we just wrote to, we need a
|
||||
// pipeline barrier between framebuffer writes and shader reads.
|
||||
if (!rt->isSwapChain()) {
|
||||
for (auto& attachment: mRenderPassFboInfo.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rt->emitBarriersEndRenderPass(*cmdbuf);
|
||||
|
||||
mRenderPassFboInfo.clear();
|
||||
mRenderPassFboInfo = {};
|
||||
mCurrentRenderPass.renderTarget = nullptr;
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
FVK_SYSTRACE_END();
|
||||
@@ -1526,17 +1421,18 @@ void VulkanDriver::nextSubpass(int) {
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.currentSubpass == 0)
|
||||
<< "Only two subpasses are currently supported.";
|
||||
|
||||
VulkanCommandBuffer* cmdbuf = mCurrentRenderPass.cmdBuffer;
|
||||
VulkanRenderTarget* renderTarget = mCurrentRenderPass.renderTarget;
|
||||
assert_invariant(renderTarget);
|
||||
assert_invariant(mCurrentRenderPass.params.subpassMask);
|
||||
|
||||
vkCmdNextSubpass(mCommands.get().buffer(), VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdNextSubpass(cmdbuf->buffer(), VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
|
||||
++mCurrentRenderPass.currentSubpass);
|
||||
|
||||
if (mCurrentRenderPass.params.subpassMask & 0x1) {
|
||||
VulkanAttachment subpassInput = renderTarget->getColor(0);
|
||||
VulkanAttachment subpassInput = renderTarget->getColor0();
|
||||
mDescriptorSetManager.updateInputAttachment({}, subpassInput);
|
||||
}
|
||||
}
|
||||
@@ -1578,8 +1474,8 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
|
||||
VulkanCommands* commands = &mCommands;
|
||||
mBoundPipeline.program->writePushConstant(commands, mBoundPipeline.pipelineLayout, stage, index,
|
||||
VulkanCommandBuffer* cmdbuf = mCurrentRenderPass.cmdBuffer;
|
||||
mBoundPipeline.program->writePushConstant(cmdbuf, mBoundPipeline.pipelineLayout, stage, index,
|
||||
value);
|
||||
}
|
||||
|
||||
@@ -1590,6 +1486,7 @@ void VulkanDriver::insertEventMarker(char const* string) {
|
||||
}
|
||||
|
||||
void VulkanDriver::pushGroupMarker(char const* string) {
|
||||
FVK_LOGE <<"---> " << string << utils::io::endl;
|
||||
// Turns out all the markers are 0-terminated, so we can just pass it without len.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands.pushGroupMarker(string);
|
||||
@@ -1760,17 +1657,17 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
|
||||
|
||||
mBlitter.blit(vkfilter,
|
||||
dstTarget->getColor(0), dstOffsets,
|
||||
srcTarget->getColor(0), srcOffsets);
|
||||
dstTarget->getColor0(), dstOffsets,
|
||||
srcTarget->getColor0(), srcOffsets);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw");
|
||||
FVK_SYSTRACE_START("bindPipeline");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands.get();
|
||||
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
|
||||
const VulkanVertexBufferInfo& vbi =
|
||||
*mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(pipelineState.vertexBufferInfo);
|
||||
|
||||
@@ -1781,6 +1678,8 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
|
||||
commands->acquire(program);
|
||||
|
||||
FVK_LOGE <<"bindpipeline with cmdbuf=" << commands->buffer() << utils::io::endl;
|
||||
|
||||
// Update the VK raster state.
|
||||
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
|
||||
@@ -1830,7 +1729,7 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
}
|
||||
auto layout = mResourceAllocator.handle_cast<VulkanDescriptorSetLayout*>(handle);
|
||||
layoutCount++;
|
||||
return layout->vklayout;
|
||||
return layout->getVkLayout();
|
||||
});
|
||||
auto pipelineLayout = mPipelineLayoutCache.getLayout(layoutList, program);
|
||||
|
||||
@@ -1845,11 +1744,6 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
mPipelineCache.bindLayout(pipelineLayout);
|
||||
mPipelineCache.bindPipeline(commands);
|
||||
|
||||
// Since we don't statically define scissor as part of the pipeline, we need to call scissor at
|
||||
// least once. Context: VUID-vkCmdDrawIndexed-None-07832.
|
||||
auto const& extent = rt->getExtent();
|
||||
scissor({0, 0, extent.width, extent.height});
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
@@ -1857,7 +1751,7 @@ void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("bindRenderPrimitive");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands.get();
|
||||
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
commands->acquire(prim.indexBuffer);
|
||||
@@ -1894,10 +1788,12 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw2");
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands.get();
|
||||
VkCommandBuffer cmdbuffer = commands.buffer();
|
||||
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
|
||||
mDescriptorSetManager.commit(&commands, mBoundPipeline.pipelineLayout,
|
||||
FVK_LOGE <<"draw2 with cmdbuf=" << cmdbuffer << utils::io::endl;
|
||||
|
||||
mDescriptorSetManager.commit(commands, mBoundPipeline.pipelineLayout,
|
||||
mBoundPipeline.descriptorSetMask);
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
@@ -1925,8 +1821,11 @@ void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
|
||||
}
|
||||
|
||||
void VulkanDriver::scissor(Viewport scissorBox) {
|
||||
VulkanCommandBuffer& commands = mCommands.get();
|
||||
VkCommandBuffer cmdbuffer = commands.buffer();
|
||||
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
|
||||
// TODO: it's a common case that scissor() is called with (0, 0, maxint, maxint)
|
||||
// we should maybe have a fast path for this and avoid vkCmdSetScissor() if possible
|
||||
|
||||
// Set scissoring.
|
||||
// clamp left-bottom to 0,0 and avoid overflows
|
||||
|
||||
@@ -47,21 +47,6 @@ struct VulkanSamplerGroup;
|
||||
constexpr uint8_t MAX_RENDERTARGET_ATTACHMENT_TEXTURES =
|
||||
MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT * 2 + 1;
|
||||
|
||||
// We need to store information about a render pass to enable better barriers at the end of a
|
||||
// renderpass.
|
||||
struct RenderPassFboBundle {
|
||||
using AttachmentArray =
|
||||
CappedArray<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
|
||||
|
||||
AttachmentArray attachments;
|
||||
bool hasColorResolve = false;
|
||||
|
||||
void clear() {
|
||||
attachments.clear();
|
||||
hasColorResolve = false;
|
||||
}
|
||||
};
|
||||
|
||||
class VulkanDriver final : public DriverBase {
|
||||
public:
|
||||
static Driver* create(VulkanPlatform* platform, VulkanContext const& context,
|
||||
@@ -130,10 +115,6 @@ private:
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
std::unique_ptr<VulkanTimestamps> mTimestamps;
|
||||
|
||||
// Placeholder resources
|
||||
VulkanTexture* mEmptyTexture;
|
||||
VulkanBufferObject* mEmptyBufferObject;
|
||||
|
||||
VulkanSwapChain* mCurrentSwapChain = nullptr;
|
||||
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
|
||||
VulkanRenderPass mCurrentRenderPass = {};
|
||||
@@ -160,13 +141,18 @@ private:
|
||||
VulkanDescriptorSetManager mDescriptorSetManager;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
struct BoundPipeline {
|
||||
struct {
|
||||
VulkanProgram* program;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
DescriptorSetMask descriptorSetMask;
|
||||
};
|
||||
BoundPipeline mBoundPipeline = {};
|
||||
RenderPassFboBundle mRenderPassFboInfo;
|
||||
} mBoundPipeline = {};
|
||||
|
||||
// We need to store information about a render pass to enable better barriers at the end of a
|
||||
// renderpass.
|
||||
struct {
|
||||
using AttachmentArray = CappedArray<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
|
||||
AttachmentArray attachments;
|
||||
} mRenderPassFboInfo = {};
|
||||
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
backend::StereoscopicType const mStereoscopicType;
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace filament::backend {
|
||||
|
||||
bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
|
||||
const RenderPassKey& k2) const {
|
||||
if (k1.initialColorLayoutMask != k2.initialColorLayoutMask) return false;
|
||||
if (k1.initialDepthLayout != k2.initialDepthLayout) return false;
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (k1.colorFormat[i] != k2.colorFormat[i]) return false;
|
||||
@@ -42,6 +41,7 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
|
||||
if (k1.discardEnd != k2.discardEnd) return false;
|
||||
if (k1.samples != k2.samples) return false;
|
||||
if (k1.needsResolveMask != k2.needsResolveMask) return false;
|
||||
if (k1.usesLazilyAllocatedMemory != k2.usesLazilyAllocatedMemory) return false;
|
||||
if (k1.subpassMask != k2.subpassMask) return false;
|
||||
if (k1.viewCount != k2.viewCount) return false;
|
||||
return true;
|
||||
@@ -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;
|
||||
@@ -255,12 +255,10 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
.format = config.colorFormat[i],
|
||||
.samples = (VkSampleCountFlagBits) config.samples,
|
||||
.loadOp = clear ? kClear : (discard ? kDontCare : kKeep),
|
||||
.storeOp = kEnableStore,
|
||||
.storeOp = (config.usesLazilyAllocatedMemory & (1 << i)) ? kDisableStore : kEnableStore,
|
||||
.stencilLoadOp = kDontCare,
|
||||
.stencilStoreOp = kDisableStore,
|
||||
.initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear)
|
||||
? imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT)
|
||||
: imgutil::getVkLayout(VulkanLayout::UNDEFINED),
|
||||
.initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
|
||||
.finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT),
|
||||
};
|
||||
}
|
||||
@@ -364,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]--;
|
||||
|
||||
@@ -42,36 +42,25 @@ public:
|
||||
// RenderPassKey is a small POD representing the immutable state that is used to construct
|
||||
// a VkRenderPass. It is hashed and used as a lookup key.
|
||||
struct alignas(8) RenderPassKey {
|
||||
// For each target, we need to know three image layouts: the layout BEFORE the pass, the
|
||||
// layout DURING the pass, and the layout AFTER the pass. Here are the rules:
|
||||
// - For depth, we explicitly specify all three layouts.
|
||||
// - Color targets have their initial image layout specified with a bitmask.
|
||||
// - For each color target, the pre-existing layout is either UNDEFINED (0) or GENERAL (1).
|
||||
// - The render pass and final images layout for color buffers is always
|
||||
// VulkanLayout::COLOR_ATTACHMENT.
|
||||
uint8_t initialColorLayoutMask;
|
||||
|
||||
// Note that if VulkanLayout grows beyond 16, we'd need to up this.
|
||||
VulkanLayout initialDepthLayout : 8;
|
||||
uint8_t padding0;
|
||||
uint8_t padding1;
|
||||
|
||||
VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes
|
||||
VkFormat depthFormat; // 4 bytes
|
||||
TargetBufferFlags clear; // 4 bytes
|
||||
TargetBufferFlags discardStart; // 4 bytes
|
||||
TargetBufferFlags discardEnd; // 4 bytes
|
||||
|
||||
VulkanLayout initialDepthLayout; // 1 byte
|
||||
uint8_t samples; // 1 byte
|
||||
uint8_t needsResolveMask; // 1 byte
|
||||
uint8_t usesLazilyAllocatedMemory; // 1 byte
|
||||
uint8_t subpassMask; // 1 byte
|
||||
uint8_t viewCount; // 1 byte
|
||||
uint8_t padding[2];
|
||||
};
|
||||
struct RenderPassVal {
|
||||
VkRenderPass handle;
|
||||
uint32_t timestamp;
|
||||
};
|
||||
static_assert(0 == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT % 8);
|
||||
static_assert(sizeof(RenderPassKey::initialColorLayoutMask) == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT / 8);
|
||||
static_assert(sizeof(TargetBufferFlags) == 4, "TargetBufferFlags has unexpected size.");
|
||||
static_assert(sizeof(VkFormat) == 4, "VkFormat has unexpected size.");
|
||||
static_assert(sizeof(RenderPassKey) == 56, "RenderPassKey has unexpected size.");
|
||||
@@ -109,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;
|
||||
@@ -122,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;
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
|
||||
// TODO: remove this by moving DebugUtils out of VulkanDriver
|
||||
#include "VulkanDriver.h"
|
||||
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanUtility.h"
|
||||
@@ -32,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);
|
||||
@@ -62,13 +63,6 @@ inline void fromStageFlags(backend::ShaderStageFlags stage, descriptor_binding_t
|
||||
}
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device,
|
||||
VkDescriptorSetLayoutCreateInfo const& info) {
|
||||
VkDescriptorSetLayout layout;
|
||||
vkCreateDescriptorSetLayout(device, &info, VKALLOC, &layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
|
||||
switch(stage) {
|
||||
case backend::ShaderStage::VERTEX:
|
||||
@@ -80,61 +74,6 @@ inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
|
||||
}
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayoutCreateInfo getLayoutCreateInfo(DescriptorSetLayout const& layout) {
|
||||
// Note that the following *needs* to be static so that VkDescriptorSetLayoutCreateInfo will not
|
||||
// refer to stack memory.
|
||||
static VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
|
||||
for (auto const& binding: layout.bindings) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
VkDescriptorType type;
|
||||
|
||||
if ((binding.stageFlags & ShaderStageFlags::VERTEX) != ShaderStageFlags::NONE) {
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
}
|
||||
if ((binding.stageFlags & ShaderStageFlags::FRAGMENT) != ShaderStageFlags::NONE) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
assert_invariant(stages != 0);
|
||||
|
||||
switch (binding.type) {
|
||||
case DescriptorType::UNIFORM_BUFFER: {
|
||||
type = (binding.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE
|
||||
? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
|
||||
: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SAMPLER: {
|
||||
type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
break;
|
||||
}
|
||||
case DescriptorType::INPUT_ATTACHMENT: {
|
||||
type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
||||
break;
|
||||
}
|
||||
case DescriptorType::SHADER_STORAGE_BUFFER:
|
||||
PANIC_POSTCONDITION("Shader storage is not supported.");
|
||||
break;
|
||||
}
|
||||
toBind[count++] = {
|
||||
.binding = binding.binding,
|
||||
.descriptorType = type,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
return dlinfo;
|
||||
}
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
|
||||
BitmaskGroup mask;
|
||||
@@ -164,23 +103,36 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
VulkanTexture* initMsaaTexture(VulkanTexture* texture, VkDevice device,
|
||||
VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanResourceAllocator* handleAllocator, 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 = new VulkanTexture(device, physicalDevice, context, allocator, commands,
|
||||
handleAllocator, texture->target, levels,
|
||||
texture->format, samples, texture->width, texture->height, texture->depth, usage,
|
||||
stagePool, true /* heap allocated */);
|
||||
texture->setSidecar(msTexture);
|
||||
}
|
||||
return msTexture;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device,
|
||||
DescriptorSetLayout const& layout)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), mDevice(device),
|
||||
vklayout(createDescriptorSetLayout(device, getLayoutCreateInfo(layout))),
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
|
||||
bitmask(fromBackendLayout(layout)),
|
||||
count(Count::fromLayoutBitmask(bitmask)) {
|
||||
}
|
||||
|
||||
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
|
||||
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
|
||||
}
|
||||
count(Count::fromLayoutBitmask(bitmask)) {}
|
||||
|
||||
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
|
||||
mResources.acquire(texture);
|
||||
mTextures[mTextureCount++] = texture;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
|
||||
@@ -210,9 +162,8 @@ PushConstantDescription::PushConstantDescription(backend::Program const& program
|
||||
}
|
||||
}
|
||||
|
||||
void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
void PushConstantDescription::write(VulkanCommandBuffer* 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)) {
|
||||
@@ -300,147 +251,241 @@ 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),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(false),
|
||||
mResources(nullptr),
|
||||
mInfo(std::make_unique<Auxiliary>()) {
|
||||
mInfo->rpkey.samples = mInfo->fbkey.samples = 1;
|
||||
}
|
||||
|
||||
void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
|
||||
assert_invariant(!mOffscreen);
|
||||
|
||||
VkExtent2D const extent = swapChain.getExtent();
|
||||
mColor[0] = { .texture = swapChain.getCurrentColor() };
|
||||
mDepth = { .texture = swapChain.getDepth() };
|
||||
width = extent.width;
|
||||
height = extent.height;
|
||||
VulkanAttachment 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;
|
||||
|
||||
VulkanAttachment depth = {};
|
||||
rpkey.depthFormat = depth.getFormat();
|
||||
fbkey.depth = VK_NULL_HANDLE;
|
||||
|
||||
if (swapChain.getDepth()) {
|
||||
depth = {.texture = swapChain.getDepth()};
|
||||
mInfo->attachments.push_back(depth);
|
||||
mInfo->depthIndex = 1;
|
||||
|
||||
rpkey.depthFormat = depth.getFormat();
|
||||
fbkey.depth = depth.getImageView();
|
||||
}
|
||||
mInfo->colors.set(0);
|
||||
mProtected = swapChain.isProtected();
|
||||
}
|
||||
|
||||
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
uint32_t width, uint32_t height, uint8_t samples,
|
||||
VulkanResourceAllocator* handleAllocator, 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;
|
||||
mProtected(false),
|
||||
mResources(handleAllocator),
|
||||
mInfo(std::make_unique<Auxiliary>()) {
|
||||
|
||||
if (samples == 1) {
|
||||
return;
|
||||
}
|
||||
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)) {
|
||||
// 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, texture->usage,
|
||||
stagePool, true /* heap allocated */);
|
||||
texture->setSidecar(msTexture);
|
||||
VulkanAttachment& attachment = color[index];
|
||||
auto texture = attachment.texture;
|
||||
if (!texture) {
|
||||
rpkey.colorFormat[index] = VK_FORMAT_UNDEFINED;
|
||||
continue;
|
||||
}
|
||||
|
||||
mProtected = mProtected || texture->isProtected();
|
||||
|
||||
attachments.push_back(attachment);
|
||||
mInfo->colors.set(index);
|
||||
|
||||
rpkey.colorFormat[index] = attachment.getFormat();
|
||||
fbkey.color[index] = attachment.getImageView();
|
||||
fbkey.resolve[index] = VK_NULL_HANDLE;
|
||||
|
||||
mResources.acquire(attachment.texture);
|
||||
|
||||
if (samples > 1) {
|
||||
VulkanAttachment msaaAttachment = {};
|
||||
if (texture->samples == 1) {
|
||||
auto msaaTexture = initMsaaTexture(texture, device, physicalDevice, context,
|
||||
allocator, commands, handleAllocator,
|
||||
((VulkanTexture const*) 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);
|
||||
mResources.acquire(msaaAttachment.texture);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
mResources.acquire(depthTexture);
|
||||
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, handleAllocator, msLevel, samples, stagePool);
|
||||
mInfo->msaaDepthIndex = (uint8_t) attachments.size();
|
||||
attachments.push_back({ .texture = msaa, .layerCount = layerCount });
|
||||
mResources.acquire(msaa);
|
||||
}
|
||||
}
|
||||
mProtected = mProtected || depthTexture->isProtected();
|
||||
}
|
||||
|
||||
// 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),
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanFboCache.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanSwapChain.h"
|
||||
#include "VulkanTexture.h"
|
||||
@@ -41,11 +42,11 @@ namespace {
|
||||
template<typename Bitmask>
|
||||
inline uint8_t collapsedCount(Bitmask const& mask) {
|
||||
static_assert(sizeof(mask) <= 64);
|
||||
constexpr uint64_t VERTEX_MASK = (1ULL << getVertexStageShift<Bitmask>()) - 1ULL;
|
||||
constexpr uint64_t FRAGMENT_MASK = (1ULL << getFragmentStageShift<Bitmask>()) - 1ULL;
|
||||
constexpr uint64_t VERTEX_MASK = (1ULL << getFragmentStageShift<Bitmask>()) - 1ULL;
|
||||
constexpr uint64_t FRAGMENT_MASK = (VERTEX_MASK << getFragmentStageShift<Bitmask>());
|
||||
uint64_t val = mask.getValue();
|
||||
val = ((val | VERTEX_MASK) >> getVertexStageShift<Bitmask>()) |
|
||||
((val | FRAGMENT_MASK) >> getFragmentStageShift<Bitmask>());
|
||||
val = ((val & VERTEX_MASK) >> getVertexStageShift<Bitmask>()) |
|
||||
((val & FRAGMENT_MASK) >> getFragmentStageShift<Bitmask>());
|
||||
return (uint8_t) Bitmask(val).count();
|
||||
}
|
||||
|
||||
@@ -114,50 +115,61 @@ struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout
|
||||
}
|
||||
};
|
||||
|
||||
VulkanDescriptorSetLayout(VkDevice device, DescriptorSetLayout const& layout);
|
||||
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
|
||||
|
||||
~VulkanDescriptorSetLayout();
|
||||
~VulkanDescriptorSetLayout() = default;
|
||||
|
||||
VkDescriptorSetLayout getVkLayout() const { return mVkLayout; }
|
||||
void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; }
|
||||
|
||||
VkDevice const mDevice;
|
||||
VkDescriptorSetLayout const vklayout;
|
||||
Bitmask const bitmask;
|
||||
Count const count;
|
||||
|
||||
private:
|
||||
VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
struct VulkanDescriptorSet : public VulkanResource, HwDescriptorSet {
|
||||
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()>;
|
||||
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
||||
|
||||
VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
|
||||
UniformBufferBitmask const& dynamicUboMask,
|
||||
uint8_t uniqueDynamicUboCount,
|
||||
OnRecycle&& onRecycleFn)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
|
||||
vkSet(rawSet),
|
||||
dynamicUboMask(dynamicUboMask),
|
||||
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||
mResources(allocator),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
|
||||
~VulkanDescriptorSet() {
|
||||
if (mOnRecycleFn) {
|
||||
mOnRecycleFn();
|
||||
mOnRecycleFn(this);
|
||||
}
|
||||
}
|
||||
|
||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||
mOffsets = std::move(offsets);
|
||||
}
|
||||
|
||||
backend::DescriptorSetOffsetArray const* getOffsets() {
|
||||
return &mOffsets;
|
||||
}
|
||||
|
||||
void acquire(VulkanTexture* texture);
|
||||
|
||||
void acquire(VulkanBufferObject* texture);
|
||||
|
||||
bool hasTexture(VulkanTexture* texture) {
|
||||
return std::any_of(mTextures.begin(), mTextures.end(),
|
||||
[texture](auto t) { return t == texture; });
|
||||
}
|
||||
|
||||
// TODO: maybe change to fixed size for performance.
|
||||
VkDescriptorSet const vkSet;
|
||||
UniformBufferBitmask const dynamicUboMask;
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
private:
|
||||
std::array<VulkanTexture*, 16> mTextures = { nullptr };
|
||||
uint8_t mTextureCount = 0;
|
||||
backend::DescriptorSetOffsetArray mOffsets;
|
||||
VulkanAcquireOnlyResourceManager mResources;
|
||||
OnRecycle mOnRecycleFn;
|
||||
};
|
||||
@@ -173,7 +185,7 @@ struct PushConstantDescription {
|
||||
|
||||
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
|
||||
|
||||
void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage,
|
||||
void write(VulkanCommandBuffer* cmdbuf, VkPipelineLayout layout, backend::ShaderStage stage,
|
||||
uint8_t index, backend::PushConstantVariant const& value);
|
||||
|
||||
private:
|
||||
@@ -198,14 +210,6 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
|
||||
|
||||
inline utils::FixedCapacityVector<uint16_t> const& getBindingToSamplerIndex() const {
|
||||
return mInfo->bindingToSamplerIndex;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -214,9 +218,9 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
return mInfo->pushConstantDescription.getVkRanges();
|
||||
}
|
||||
|
||||
inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
inline void writePushConstant(VulkanCommandBuffer* 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
|
||||
@@ -232,26 +236,11 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
private:
|
||||
struct PipelineInfo {
|
||||
explicit PipelineInfo(backend::Program const& program) noexcept
|
||||
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
|
||||
pushConstantDescription(program)
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
, bindingToName(MAX_SAMPLER_COUNT, "")
|
||||
#endif
|
||||
: pushConstantDescription(program)
|
||||
{}
|
||||
|
||||
BindingList bindings;
|
||||
|
||||
// We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit.
|
||||
utils::FixedCapacityVector<uint16_t> bindingToSamplerIndex;
|
||||
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;
|
||||
@@ -280,28 +269,71 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
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 transformViewportToPlatform(VkViewport* bounds) const;
|
||||
|
||||
inline VkExtent2D getExtent() const {
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
inline VulkanAttachment const& getColor0() const {
|
||||
assert_invariant(mInfo->colors[0]);
|
||||
return mInfo->attachments[0];
|
||||
}
|
||||
|
||||
inline VulkanAttachment const& 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; }
|
||||
|
||||
bool isProtected() const { return mProtected; }
|
||||
|
||||
void bindToSwapChain(VulkanSwapChain& surf);
|
||||
|
||||
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;
|
||||
|
||||
VulkanAcquireOnlyResourceManager mResources;
|
||||
std::unique_ptr<Auxiliary> mInfo;
|
||||
};
|
||||
|
||||
struct VulkanBufferObject;
|
||||
|
||||
@@ -76,36 +76,6 @@ inline VkImageViewType getViewType(SamplerType target) {
|
||||
}
|
||||
}
|
||||
|
||||
inline VulkanLayout getDefaultLayout(TextureUsage usage) {
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
return VulkanLayout::DEPTH_SAMPLER;
|
||||
} else {
|
||||
return VulkanLayout::DEPTH_ATTACHMENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
return VulkanLayout::COLOR_ATTACHMENT;
|
||||
}
|
||||
// Finally, the layout for an immutable texture is optimal read-only.
|
||||
return VulkanLayout::READ_ONLY;
|
||||
}
|
||||
|
||||
inline VulkanLayout getDefaultLayout(VkImageUsageFlags vkusage) {
|
||||
TextureUsage usage{};
|
||||
if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
|
||||
usage = usage | TextureUsage::DEPTH_ATTACHMENT;
|
||||
}
|
||||
if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
|
||||
usage = usage | TextureUsage::COLOR_ATTACHMENT;
|
||||
}
|
||||
if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) {
|
||||
usage = usage | TextureUsage::SAMPLEABLE;
|
||||
}
|
||||
return getDefaultLayout(usage);
|
||||
}
|
||||
|
||||
constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) {
|
||||
switch (layout) {
|
||||
case VulkanLayout::UNDEFINED:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -143,7 +143,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
|
||||
VkCommandPool& cmdpool = mCommandPool;
|
||||
|
||||
VulkanTexture* srcTexture = srcTarget->getColor(0).texture;
|
||||
VulkanTexture* srcTexture = srcTarget->getColor0().texture;
|
||||
assert_invariant(srcTexture);
|
||||
VkFormat const srcFormat = srcTexture->getVkFormat();
|
||||
bool const swizzle
|
||||
@@ -230,9 +230,9 @@ 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);
|
||||
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
|
||||
VkImageCopy const imageCopyRegion = {
|
||||
.srcSubresource = {
|
||||
@@ -268,7 +268,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
|
||||
|
||||
// Restore the source image layout.
|
||||
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT);
|
||||
srcTexture->transitionLayout(cmdbuffer, srcRange, srcTexture->getDefaultLayout());
|
||||
|
||||
vkEndCommandBuffer(cmdbuffer);
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
mTransitionSwapChainImageLayoutForPresent(
|
||||
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
|
||||
mAcquired(false),
|
||||
mIsFirstRenderPass(true) {
|
||||
mIsFirstRenderPass(true),
|
||||
mIsProtected(false) {
|
||||
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
|
||||
FILAMENT_CHECK_POSTCONDITION(swapChain) << "Unable to create swapchain";
|
||||
|
||||
@@ -63,21 +64,28 @@ void VulkanSwapChain::update() {
|
||||
mColors.reserve(bundle.colors.size());
|
||||
VkDevice const device = mPlatform->getDevice();
|
||||
|
||||
mIsProtected = bundle.protection;
|
||||
TextureUsage depthUsage = TextureUsage::DEPTH_ATTACHMENT;
|
||||
TextureUsage colorUsage = TextureUsage::COLOR_ATTACHMENT;
|
||||
if (bundle.protection) {
|
||||
depthUsage |= TextureUsage::PROTECTED;
|
||||
colorUsage |= TextureUsage::PROTECTED;
|
||||
}
|
||||
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 */));
|
||||
colorUsage, mStagePool, true /* heap allocated */));
|
||||
}
|
||||
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 */);
|
||||
depthUsage, mStagePool, true /* heap allocated */);
|
||||
|
||||
mExtent = bundle.extent;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::present() {
|
||||
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VulkanCommandBuffer& commands = mIsProtected ? mCommands->getProtected() : mCommands->get();
|
||||
VkImageSubresourceRange const subresources{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
|
||||
@@ -74,6 +74,10 @@ 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;
|
||||
|
||||
@@ -97,6 +101,7 @@ private:
|
||||
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
|
||||
bool mAcquired;
|
||||
bool mIsFirstRenderPass;
|
||||
bool mIsProtected;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -88,32 +88,78 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
|
||||
return out;
|
||||
};
|
||||
|
||||
auto const identityToChannel = [](VkComponentSwizzle val, uint8_t channelIndex) {
|
||||
if (val != VK_COMPONENT_SWIZZLE_IDENTITY) {
|
||||
return val;
|
||||
}
|
||||
return IDENTITY[channelIndex];
|
||||
};
|
||||
|
||||
// We make sure all all identities are mapped into respective channels so that actual channel
|
||||
// mapping will be passed onto the output.
|
||||
VkComponentMapping const prevExplicit = {
|
||||
identityToChannel(prev.r, 0),
|
||||
identityToChannel(prev.g, 1),
|
||||
identityToChannel(prev.b, 2),
|
||||
identityToChannel(prev.a, 3),
|
||||
};
|
||||
|
||||
// Note that the channel index corresponds to the VkComponentMapping struct layout.
|
||||
return {
|
||||
compose(next.r, prev, 0),
|
||||
compose(next.g, prev, 1),
|
||||
compose(next.b, prev, 2),
|
||||
compose(next.a, prev, 3),
|
||||
compose(next.r, prevExplicit, 0),
|
||||
compose(next.g, prevExplicit, 1),
|
||||
compose(next.b, prevExplicit, 2),
|
||||
compose(next.a, prevExplicit, 3),
|
||||
};
|
||||
}
|
||||
|
||||
inline VulkanLayout getDefaultLayoutImpl(TextureUsage usage) {
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
return VulkanLayout::DEPTH_SAMPLER;
|
||||
} else {
|
||||
return VulkanLayout::DEPTH_ATTACHMENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
return VulkanLayout::COLOR_ATTACHMENT;
|
||||
}
|
||||
// Finally, the layout for an immutable texture is optimal read-only.
|
||||
return VulkanLayout::READ_ONLY;
|
||||
}
|
||||
|
||||
inline VulkanLayout getDefaultLayoutImpl(VkImageUsageFlags vkusage) {
|
||||
TextureUsage usage{};
|
||||
if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
|
||||
usage = usage | TextureUsage::DEPTH_ATTACHMENT;
|
||||
}
|
||||
if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
|
||||
usage = usage | TextureUsage::COLOR_ATTACHMENT;
|
||||
}
|
||||
if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) {
|
||||
usage = usage | TextureUsage::SAMPLEABLE;
|
||||
}
|
||||
return getDefaultLayoutImpl(usage);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanTextureState::VulkanTextureState(
|
||||
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanStagePool& stagePool,
|
||||
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount)
|
||||
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
|
||||
mVkFormat(format),
|
||||
mViewType(viewType),
|
||||
mFullViewRange {
|
||||
filament::backend::getImageAspect(format), 0, levels, 0, layerCount
|
||||
},
|
||||
mStagePool(stagePool),
|
||||
mDevice(device),
|
||||
mAllocator(allocator),
|
||||
mCommands(commands) {
|
||||
}
|
||||
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
|
||||
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout,
|
||||
bool isProtected)
|
||||
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
|
||||
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);
|
||||
@@ -125,6 +171,7 @@ VulkanTextureState const* VulkanTexture::getSharedState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Constructor for internally passed VkImage
|
||||
VulkanTexture::VulkanTexture(
|
||||
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
@@ -137,12 +184,15 @@ VulkanTexture::VulkanTexture(
|
||||
mAllocator(handleAllocator),
|
||||
mState(handleAllocator->initHandle<VulkanTextureState>(
|
||||
device, allocator, commands, stagePool,
|
||||
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1)) {
|
||||
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1,
|
||||
getDefaultLayoutImpl(tusage),
|
||||
any(usage& TextureUsage::PROTECTED))) {
|
||||
auto* const state = getSharedState();
|
||||
state->mTextureImage = image;
|
||||
mPrimaryViewRange = state->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,
|
||||
@@ -154,7 +204,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
mAllocator(handleAllocator),
|
||||
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
|
||||
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
|
||||
getLayerCount(target, depth))) {
|
||||
getLayerCount(target, depth), VulkanLayout::UNDEFINED, any(usage & TextureUsage::PROTECTED))) {
|
||||
auto* const state = getSharedState();
|
||||
|
||||
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
||||
@@ -185,6 +235,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.arrayLayers = depth * 6;
|
||||
imageInfo.extent.depth = 1;
|
||||
}
|
||||
if (state->mIsProtected) {
|
||||
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
@@ -193,6 +246,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
|
||||
// Determine if we can use the transient usage flag combined with lazily allocated memory.
|
||||
const bool useTransientAttachment =
|
||||
// Lazily allocated memory is available.
|
||||
context.isLazilyAllocatedMemorySupported() &&
|
||||
// Usage consists of attachment flags only.
|
||||
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(tusage & TextureUsage::ALL_ATTACHMENTS);
|
||||
state->mIsTransientAttachment = useTransientAttachment;
|
||||
|
||||
const VkImageUsageFlags transientFlag =
|
||||
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
@@ -208,19 +274,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag;
|
||||
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
}
|
||||
}
|
||||
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||
}
|
||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||
|
||||
// Depth resolves uses a custom shader and therefore needs to be sampleable.
|
||||
if (samples > 1) {
|
||||
@@ -269,8 +335,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VkMemoryRequirements memReqs = {};
|
||||
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
|
||||
|
||||
const VkFlags requiredMemoryFlags =
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
|
||||
(state->mIsProtected ? 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)
|
||||
<< "VulkanTexture: unable to find a memory type that meets requirements.";
|
||||
@@ -291,20 +361,24 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
// Go ahead and create the primary image view.
|
||||
getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
|
||||
|
||||
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
|
||||
VulkanCommandBuffer& commandsBuf = state->mIsProtected ? state->mCommands->getProtected()
|
||||
: state->mCommands->get();
|
||||
|
||||
commandsBuf.acquire(this);
|
||||
transitionLayout(&commandsBuf, mPrimaryViewRange, imgutil::getDefaultLayout(imageInfo.usage));
|
||||
|
||||
auto const defaultLayout = state->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
|
||||
transitionLayout(&commandsBuf, mPrimaryViewRange, defaultLayout);
|
||||
}
|
||||
|
||||
// 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, 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)
|
||||
{
|
||||
VulkanResourceAllocator* handleAllocator, VulkanTexture const* 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) {
|
||||
mState = src->mState;
|
||||
auto* state = getSharedState();
|
||||
|
||||
@@ -314,14 +388,15 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
mPrimaryViewRange.levelCount = levelCount;
|
||||
}
|
||||
|
||||
// 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)
|
||||
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage),
|
||||
VulkanResource(VulkanResourceType::TEXTURE),
|
||||
mAllocator(handleAllocator) {
|
||||
VulkanResourceAllocator* handleAllocator, VulkanTexture const* 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) {
|
||||
mState = src->mState;
|
||||
auto* state = getSharedState();
|
||||
state->refs++;
|
||||
@@ -350,6 +425,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP ||
|
||||
target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1));
|
||||
auto* const state = getSharedState();
|
||||
assert_invariant(!state->mIsProtected);
|
||||
const PixelBufferDescriptor* hostData = &data;
|
||||
PixelBufferDescriptor reshapedData;
|
||||
|
||||
@@ -381,7 +457,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
vmaUnmapMemory(state->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size);
|
||||
|
||||
VulkanCommandBuffer& commands = state->mCommands->get();
|
||||
VulkanCommandBuffer& commands =state->mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(this);
|
||||
|
||||
@@ -424,7 +500,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
VkImageLayout const newVkLayout = imgutil::getVkLayout(newLayout);
|
||||
|
||||
if (nextLayout == VulkanLayout::UNDEFINED) {
|
||||
nextLayout = imgutil::getDefaultLayout(this->usage);
|
||||
nextLayout = getDefaultLayout();
|
||||
}
|
||||
|
||||
transitionLayout(&commands, transitionRange, newLayout);
|
||||
@@ -437,6 +513,8 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel) {
|
||||
auto* const state = getSharedState();
|
||||
assert_invariant(!state->mIsProtected);
|
||||
|
||||
void* mapped = nullptr;
|
||||
VulkanStageImage const* stage
|
||||
= state->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
|
||||
@@ -475,6 +553,11 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
||||
transitionLayout(&commands, range, oldLayout);
|
||||
}
|
||||
|
||||
VulkanLayout VulkanTexture::getDefaultLayout() const {
|
||||
auto* const state = getSharedState();
|
||||
return state->mDefaultLayout;
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
||||
range.levelCount = 1;
|
||||
range.layerCount = 1;
|
||||
@@ -520,13 +603,11 @@ VkImageAspectFlags VulkanTexture::getImageAspect() const {
|
||||
}
|
||||
|
||||
bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
|
||||
const VkImageSubresourceRange& range, VulkanLayout newLayout) {
|
||||
return transitionLayout(commands->buffer(), commands->fence, range, newLayout);
|
||||
VkImageSubresourceRange const& range, VulkanLayout newLayout) {
|
||||
return transitionLayout(commands->buffer(), range, newLayout);
|
||||
}
|
||||
|
||||
bool VulkanTexture::transitionLayout(
|
||||
VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
|
||||
const VkImageSubresourceRange& range,
|
||||
bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range,
|
||||
VulkanLayout newLayout) {
|
||||
auto* const state = getSharedState();
|
||||
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
|
||||
@@ -581,10 +662,11 @@ bool VulkanTexture::transitionLayout(
|
||||
});
|
||||
}
|
||||
|
||||
if (hasTransitions) {
|
||||
state->mTransitionFence = fence;
|
||||
setLayout(range, newLayout);
|
||||
// Even if we didn't carry out the transition, we should assume that the new layout is defined
|
||||
// through this call.
|
||||
setLayout(range, newLayout);
|
||||
|
||||
if (hasTransitions) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
|
||||
FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer
|
||||
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
|
||||
@@ -603,6 +685,30 @@ bool VulkanTexture::transitionLayout(
|
||||
return hasTransitions;
|
||||
}
|
||||
|
||||
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 = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.dstAccessMask =
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.oldLayout = layout,
|
||||
.newLayout = layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = state->mTextureImage,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
}
|
||||
|
||||
void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
|
||||
VkImageSubresourceRange const& range) {
|
||||
VkCommandBuffer const cmdbuf = commands->buffer();
|
||||
@@ -663,9 +769,9 @@ VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
|
||||
void VulkanTexture::print() const {
|
||||
auto* const state = getSharedState();
|
||||
uint32_t const firstLayer = 0;
|
||||
uint32_t const lastLayer = firstLayer + mFullViewRange.layerCount;
|
||||
uint32_t const lastLayer = firstLayer + state->mFullViewRange.layerCount;
|
||||
uint32_t const firstLevel = 0;
|
||||
uint32_t const lastLevel = firstLevel + mFullViewRange.levelCount;
|
||||
uint32_t const lastLevel = firstLevel + state->mFullViewRange.levelCount;
|
||||
|
||||
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
|
||||
for (uint32_t level = firstLevel; level < lastLevel; ++level) {
|
||||
|
||||
@@ -34,8 +34,9 @@ class VulkanResourceAllocator;
|
||||
|
||||
struct VulkanTextureState : public VulkanResource {
|
||||
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanStagePool& stagePool,
|
||||
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount);
|
||||
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
|
||||
uint8_t layerCount, VulkanLayout defaultLayout,
|
||||
bool isProtected = false);
|
||||
|
||||
struct ImageViewKey {
|
||||
VkImageSubresourceRange range; // 4 * 5 bytes
|
||||
@@ -67,8 +68,10 @@ struct VulkanTextureState : public VulkanResource {
|
||||
VkFormat const mVkFormat;
|
||||
VkImageViewType const mViewType;
|
||||
VkImageSubresourceRange const mFullViewRange;
|
||||
|
||||
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;
|
||||
@@ -78,7 +81,7 @@ struct VulkanTextureState : public VulkanResource {
|
||||
VkDevice mDevice;
|
||||
VmaAllocator mAllocator;
|
||||
VulkanCommands* mCommands;
|
||||
std::shared_ptr<VulkanCmdFence> mTransitionFence;
|
||||
bool mIsTransientAttachment;
|
||||
};
|
||||
|
||||
|
||||
@@ -111,6 +114,13 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
VulkanTexture const* src, VkComponentMapping swizzle);
|
||||
|
||||
// Specialized constructor for external images
|
||||
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanResourceAllocator* handleAllocator,
|
||||
VkImage image, VkDeviceMemory memory,
|
||||
VkFormat format, uint32_t width, uint32_t height, TextureUsage tusage,
|
||||
VulkanStagePool& stagePool);
|
||||
|
||||
~VulkanTexture();
|
||||
|
||||
// Uploads data into a subregion of a 2D or 3D texture.
|
||||
@@ -134,6 +144,10 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
|
||||
}
|
||||
|
||||
// Returns the layout for the intended use of this texture (and not the expected layout at the
|
||||
// time of the call.
|
||||
VulkanLayout getDefaultLayout() const;
|
||||
|
||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
||||
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
|
||||
// and the identity swizzle.
|
||||
@@ -170,15 +184,28 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
return state->mSidecarMSAA.get();
|
||||
}
|
||||
|
||||
bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range,
|
||||
bool isTransientAttachment() const {
|
||||
VulkanTextureState const* state = getSharedState();
|
||||
return state->mIsTransientAttachment;
|
||||
}
|
||||
|
||||
bool isProtected() const {
|
||||
VulkanTextureState const* state = getSharedState();
|
||||
return state->mIsProtected;
|
||||
}
|
||||
|
||||
bool transitionLayout(VulkanCommandBuffer* commands, VkImageSubresourceRange const& range,
|
||||
VulkanLayout newLayout);
|
||||
|
||||
bool transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
|
||||
VkImageSubresourceRange const& range, VulkanLayout newLayout);
|
||||
bool transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range,
|
||||
VulkanLayout newLayout);
|
||||
|
||||
void attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
|
||||
VkImageSubresourceRange const& range);
|
||||
|
||||
void samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
|
||||
VkImageSubresourceRange const& range);
|
||||
|
||||
// Returns the preferred data plane of interest for all image views.
|
||||
// For now this always returns either DEPTH or COLOR.
|
||||
VkImageAspectFlags getImageAspect() const;
|
||||
@@ -187,13 +214,6 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
// manually (outside of calls to transitionLayout).
|
||||
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
|
||||
|
||||
bool transitionReady() {
|
||||
VulkanTextureState* state = getSharedState();
|
||||
auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS;
|
||||
state->mTransitionFence.reset();
|
||||
return res;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
void print() const;
|
||||
#endif
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
@@ -121,8 +122,9 @@ public:
|
||||
return count == mCount;
|
||||
}
|
||||
|
||||
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
|
||||
if (UnusedSetMap::iterator itr = mUnused.find(layout->bitmask); itr != mUnused.end()) {
|
||||
VkDescriptorSet obtainSet(VkDescriptorSetLayout vklayout) {
|
||||
auto itr = findSets(vklayout);
|
||||
if (itr != mUnused.end()) {
|
||||
// If we don't have any unused, then just return an empty handle.
|
||||
if (itr->second.empty()) {
|
||||
return VK_NULL_HANDLE;
|
||||
@@ -137,7 +139,7 @@ public:
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
// Creating a new set
|
||||
VkDescriptorSetLayout layouts[1] = {layout->vklayout};
|
||||
VkDescriptorSetLayout layouts[1] = {vklayout};
|
||||
VkDescriptorSetAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -154,19 +156,29 @@ public:
|
||||
return vkSet;
|
||||
}
|
||||
|
||||
void recycle(BitmaskGroup const& layoutMask, VkDescriptorSet vkSet) {
|
||||
void recycle(VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
|
||||
// We are recycling - release the set back into the pool. Note that the
|
||||
// vk handle has not changed, but we need to change the backend handle to allow
|
||||
// for proper refcounting of resources referenced in this set.
|
||||
if (auto itr = mUnused.find(layoutMask); itr != mUnused.end()) {
|
||||
auto itr = findSets(vklayout);
|
||||
if (itr != mUnused.end()) {
|
||||
itr->second.push_back(vkSet);
|
||||
} else {
|
||||
mUnused[layoutMask].push_back(vkSet);
|
||||
mUnused.push_back(std::make_pair(vklayout, std::vector<VkDescriptorSet> {vkSet}));
|
||||
}
|
||||
mUnusedCount++;
|
||||
}
|
||||
|
||||
private:
|
||||
using UnusedSets = std::pair<VkDescriptorSetLayout, std::vector<VkDescriptorSet>>;
|
||||
using UnusedSetMap = std::vector<UnusedSets>;
|
||||
|
||||
inline UnusedSetMap::iterator findSets(VkDescriptorSetLayout vklayout) {
|
||||
return std::find_if(mUnused.begin(), mUnused.end(), [vklayout](auto const& value) {
|
||||
return value.first == vklayout;
|
||||
});
|
||||
}
|
||||
|
||||
VkDevice mDevice;
|
||||
VkDescriptorPool mPool;
|
||||
DescriptorCount const mCount;
|
||||
@@ -177,16 +189,83 @@ private:
|
||||
// Tracks the number of in-use descriptor sets.
|
||||
uint16_t mUnusedCount;
|
||||
|
||||
// This maps a layout ot a list of descriptor sets allocated for that layout.
|
||||
using UnusedSetMap = std::unordered_map<BitmaskGroup, std::vector<VkDescriptorSet>,
|
||||
BitmaskGroupHashFn, BitmaskGroupEqual>;
|
||||
// This maps a layout to a list of descriptor sets allocated for that layout.
|
||||
UnusedSetMap mUnused;
|
||||
};
|
||||
|
||||
template<typename Key>
|
||||
struct Equal {
|
||||
bool operator()(Key const& k1, Key const& k2) const {
|
||||
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(Key));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Bitmask>
|
||||
uint32_t createBindings(VkDescriptorSetLayoutBinding* toBind, uint32_t count, VkDescriptorType type,
|
||||
Bitmask const& mask) {
|
||||
Bitmask alreadySeen;
|
||||
mask.forEachSetBit([&](size_t index) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
uint32_t binding = 0;
|
||||
if (index < getFragmentStageShift<Bitmask>()) {
|
||||
binding = (uint32_t) index;
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
auto fragIndex = index + getFragmentStageShift<Bitmask>();
|
||||
if (mask.test(fragIndex)) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
alreadySeen.set(fragIndex);
|
||||
}
|
||||
} else if (!alreadySeen.test(index)) {
|
||||
// We are in fragment stage bits
|
||||
binding = (uint32_t) (index - getFragmentStageShift<Bitmask>());
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
|
||||
if (stages) {
|
||||
toBind[count++] = {
|
||||
.binding = binding,
|
||||
.descriptorType = type,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayout createLayout(VkDevice device, BitmaskGroup const& bitmaskGroup) {
|
||||
// Note that the following *needs* to be static so that VkDescriptorSetLayoutCreateInfo will not
|
||||
// refer to stack memory.
|
||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmaskGroup.dynamicUbo);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmaskGroup.ubo);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
bitmaskGroup.sampler);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmaskGroup.inputAttachment);
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
|
||||
VkDescriptorSetLayout layout;
|
||||
vkCreateDescriptorSetLayout(device, &dlinfo, VKALLOC, &layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// This is an ever-expanding pool of sets where it
|
||||
// 1. Keeps a list of smaller pools of different layout-dimensions.
|
||||
// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out.
|
||||
class DescriptorInfinitePool {
|
||||
class VulkanDescriptorSetManager::DescriptorInfinitePool {
|
||||
private:
|
||||
static constexpr uint16_t EXPECTED_SET_COUNT = 10;
|
||||
static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5;
|
||||
@@ -196,12 +275,13 @@ public:
|
||||
: mDevice(device) {}
|
||||
|
||||
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
DescriptorPool* sameTypePool = nullptr;
|
||||
for (auto& pool: mPools) {
|
||||
if (!pool->canAllocate(layout->count)) {
|
||||
continue;
|
||||
}
|
||||
if (auto set = pool->obtainSet(layout); set != VK_NULL_HANDLE) {
|
||||
if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) {
|
||||
return set;
|
||||
}
|
||||
if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) {
|
||||
@@ -219,18 +299,18 @@ public:
|
||||
mPools.push_back(std::make_unique<DescriptorPool>(mDevice,
|
||||
DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity));
|
||||
auto& pool = mPools.back();
|
||||
auto ret = pool->obtainSet(layout);
|
||||
auto ret = pool->obtainSet(vklayout);
|
||||
assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?");
|
||||
return ret;
|
||||
}
|
||||
|
||||
void recycle(DescriptorCount const& count, BitmaskGroup const& layoutMask,
|
||||
void recycle(DescriptorCount const& count, VkDescriptorSetLayout vklayout,
|
||||
VkDescriptorSet vkSet) {
|
||||
for (auto& pool: mPools) {
|
||||
if (!pool->canAllocate(count)) {
|
||||
continue;
|
||||
}
|
||||
pool->recycle(layoutMask, vkSet);
|
||||
pool->recycle(vklayout, vkSet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -240,315 +320,183 @@ private:
|
||||
std::vector<std::unique_ptr<DescriptorPool>> mPools;
|
||||
};
|
||||
|
||||
template<typename Key>
|
||||
struct Equal {
|
||||
bool operator()(Key const& k1, Key const& k2) const {
|
||||
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(Key));
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class VulkanDescriptorSetManager::Impl {
|
||||
private:
|
||||
struct DescriptorSetHistory {
|
||||
private:
|
||||
using TextureBundle = std::pair<VulkanTexture*, VkImageSubresourceRange>;
|
||||
public:
|
||||
DescriptorSetHistory() : mResources(nullptr) {}
|
||||
|
||||
DescriptorSetHistory(BitmaskGroup const& mask, VulkanResourceAllocator* allocator,
|
||||
VulkanDescriptorSet* set)
|
||||
: dynamicUboMask(mask.dynamicUbo),
|
||||
mResources(allocator),
|
||||
mSet(set),
|
||||
mBound(false) {
|
||||
// initial state is unbound.
|
||||
mResources.acquire(mSet);
|
||||
unbind();
|
||||
}
|
||||
|
||||
~DescriptorSetHistory() {
|
||||
assert_invariant(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, (uint32_t) dynamicUboMask.count(), 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;
|
||||
|
||||
private:
|
||||
FixedSizeVulkanResourceManager<1> mResources;
|
||||
VulkanDescriptorSet* mSet = nullptr;
|
||||
|
||||
backend::DescriptorSetOffsetArray mOffsets;
|
||||
bool mBound = false;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
class VulkanDescriptorSetManager::DescriptorSetLayoutManager {
|
||||
public:
|
||||
Impl(VkDevice device, VulkanResourceAllocator* resourceAllocator)
|
||||
: mDevice(device),
|
||||
mResourceAllocator(resourceAllocator),
|
||||
mDescriptorPool(device) {}
|
||||
DescriptorSetLayoutManager(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
// bind() is not really binding the set but just stashing until we have all the info
|
||||
// (pipelinelayout).
|
||||
void bind(uint8_t setIndex, VulkanDescriptorSet* set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
auto& history = mHistory[set->vkSet];
|
||||
history.setOffsets(std::move(offsets));
|
||||
|
||||
auto lastHistory = mStashedSets[setIndex];
|
||||
if (lastHistory) {
|
||||
lastHistory->unbind();
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout* layout) {
|
||||
auto const& bitmasks = layout->bitmask;
|
||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
mStashedSets[setIndex] = &history;
|
||||
auto vklayout = createLayout(mDevice, layout->bitmask);
|
||||
mVkLayouts[layout->bitmask] = vklayout;
|
||||
return vklayout;
|
||||
}
|
||||
|
||||
void 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;
|
||||
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index] || updateSets[index]->bound()) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
|
||||
BoundInfo nextInfo = {
|
||||
pipelineLayout,
|
||||
setMask,
|
||||
updateSets,
|
||||
};
|
||||
if (curMask.none() && mLastBoundInfo == nextInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
|
||||
updateSets[index]->bind(commands, pipelineLayout, index);
|
||||
});
|
||||
mLastBoundInfo = nextInfo;
|
||||
}
|
||||
|
||||
void updateBuffer(VulkanDescriptorSet* set, uint8_t binding, 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->vkSet];
|
||||
|
||||
if (history.dynamicUboMask.test(binding)) {
|
||||
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
}
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set->vkSet,
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = type,
|
||||
.pBufferInfo = &info,
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
set->acquire(bufferObject);
|
||||
history.write(binding);
|
||||
}
|
||||
|
||||
void updateSampler(VulkanDescriptorSet* set, uint8_t binding, VulkanTexture* texture,
|
||||
VkSampler sampler) noexcept {
|
||||
VkDescriptorImageInfo info{
|
||||
.sampler = sampler,
|
||||
};
|
||||
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
|
||||
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
|
||||
// an attachment, then the sampler for this texture has the same view properties as a
|
||||
// view for an attachment. Therefore, we can use getAttachmentView to get a
|
||||
// corresponding VkImageView.
|
||||
info.imageView = texture->getAttachmentView(range);
|
||||
} else {
|
||||
info.imageView = texture->getViewForType(range, expectedType);
|
||||
}
|
||||
info.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout());
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set->vkSet,
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.pImageInfo = &info,
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
set->acquire(texture);
|
||||
mHistory[set->vkSet].write(binding);
|
||||
}
|
||||
|
||||
void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept {
|
||||
// TOOD: fill-in this region
|
||||
}
|
||||
|
||||
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
|
||||
VulkanBufferObject* bufferObject) noexcept {
|
||||
mPlaceHolderBufferInfo = {
|
||||
.buffer = bufferObject->buffer.getGpuBuffer(),
|
||||
.offset = 0,
|
||||
.range = 1,
|
||||
};
|
||||
mPlaceHolderImageInfo = {
|
||||
.sampler = sampler,
|
||||
.imageView = texture->getPrimaryImageView(),
|
||||
.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout()),
|
||||
};
|
||||
}
|
||||
|
||||
void createSet(Handle<HwDescriptorSet> handle, VulkanDescriptorSetLayout* layout) {
|
||||
auto const vkSet = mDescriptorPool.obtainSet(layout);
|
||||
auto const& count = layout->count;
|
||||
auto const& layoutMask = layout->bitmask;
|
||||
auto set = mResourceAllocator->construct<VulkanDescriptorSet>(handle, mResourceAllocator,
|
||||
vkSet, [vkSet, count, layoutMask, this]() {
|
||||
mHistory.erase(vkSet);
|
||||
mDescriptorPool.recycle(count, layoutMask, vkSet);
|
||||
});
|
||||
mHistory.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(vkSet),
|
||||
std::forward_as_tuple(layout->bitmask, mResourceAllocator, set));
|
||||
}
|
||||
|
||||
void destroySet(Handle<HwDescriptorSet> handle) {
|
||||
VulkanDescriptorSet* set = mResourceAllocator->handle_cast<VulkanDescriptorSet*>(handle);
|
||||
DescriptorSetHistory* history = &mHistory[set->vkSet];
|
||||
mHistory.erase(set->vkSet);
|
||||
|
||||
for (uint8_t i = 0; i < mStashedSets.size(); ++i) {
|
||||
if (mStashedSets[i] == history) {
|
||||
mStashedSets[i] = nullptr;
|
||||
}
|
||||
~DescriptorSetLayoutManager() {
|
||||
for (auto& itr: mVkLayouts) {
|
||||
vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
VkDevice mDevice;
|
||||
VulkanResourceAllocator* mResourceAllocator;
|
||||
DescriptorInfinitePool mDescriptorPool;
|
||||
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
|
||||
std::unordered_map<VkDescriptorSet, DescriptorSetHistory> mHistory;
|
||||
DescriptorSetHistoryArray mStashedSets;
|
||||
|
||||
BoundInfo mLastBoundInfo;
|
||||
|
||||
VkDescriptorBufferInfo mPlaceHolderBufferInfo;
|
||||
VkDescriptorImageInfo mPlaceHolderImageInfo;
|
||||
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
|
||||
mVkLayouts;
|
||||
};
|
||||
|
||||
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
|
||||
VulkanResourceAllocator* resourceAllocator)
|
||||
: mImpl(new Impl(device, resourceAllocator)) {}
|
||||
: mDevice(device),
|
||||
mResourceAllocator(resourceAllocator),
|
||||
mLayoutManager(std::make_unique<DescriptorSetLayoutManager>(device)),
|
||||
mDescriptorPool(std::make_unique<DescriptorInfinitePool>(device)) {}
|
||||
|
||||
void VulkanDescriptorSetManager::terminate() noexcept {
|
||||
assert_invariant(mImpl);
|
||||
delete mImpl;
|
||||
mImpl = nullptr;
|
||||
}
|
||||
VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default;
|
||||
|
||||
void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
mImpl->updateBuffer(set, binding, bufferObject, offset, size);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanTexture* texture, VkSampler sampler) noexcept {
|
||||
mImpl->updateSampler(set, binding, texture, sampler);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set,
|
||||
VulkanAttachment attachment) noexcept {
|
||||
mImpl->updateInputAttachment(set, attachment);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
|
||||
VulkanBufferObject* bufferObject) noexcept {
|
||||
mImpl->setPlaceHolders(sampler, texture, bufferObject);
|
||||
void VulkanDescriptorSetManager::terminate() noexcept{
|
||||
mLayoutManager.reset();
|
||||
mDescriptorPool.reset();
|
||||
}
|
||||
|
||||
// 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,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
return mImpl->bind(setIndex, set, std::move(offsets));
|
||||
set->setOffsets(std::move(offsets));
|
||||
mStashedSets[setIndex] = set;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
|
||||
VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) {
|
||||
mImpl->commit(commands, pipelineLayout, setMask);
|
||||
// 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] == lastBoundSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
|
||||
if (curMask.none() &&
|
||||
(mLastBoundInfo.pipelineLayout == pipelineLayout && mLastBoundInfo.setMask == setMask &&
|
||||
mLastBoundInfo.boundSets == updateSets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t 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);
|
||||
});
|
||||
|
||||
mStashedSets = {};
|
||||
|
||||
mLastBoundInfo = {
|
||||
pipelineLayout,
|
||||
setMask,
|
||||
updateSets,
|
||||
};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
|
||||
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;
|
||||
|
||||
if (set->dynamicUboMask.test(binding)) {
|
||||
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
}
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set->vkSet,
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = type,
|
||||
.pBufferInfo = &info,
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
set->acquire(bufferObject);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding,
|
||||
VulkanTexture* texture, VkSampler sampler) noexcept {
|
||||
VkDescriptorImageInfo info{
|
||||
.sampler = sampler,
|
||||
};
|
||||
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
|
||||
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
|
||||
// an attachment, then the sampler for this texture has the same view properties as a
|
||||
// view for an attachment. Therefore, we can use getAttachmentView to get a
|
||||
// corresponding VkImageView.
|
||||
info.imageView = texture->getAttachmentView(range);
|
||||
} else {
|
||||
info.imageView = texture->getViewForType(range, expectedType);
|
||||
}
|
||||
info.imageLayout = imgutil::getVkLayout(texture->getDefaultLayout());
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set->vkSet,
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.pImageInfo = &info,
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
set->acquire(texture);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set,
|
||||
VulkanAttachment attachment) noexcept {
|
||||
// TOOD: fill-in this region
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::createSet(Handle<HwDescriptorSet> handle,
|
||||
VulkanDescriptorSetLayout* layout) {
|
||||
mImpl->createSet(handle, layout);
|
||||
auto const vkSet = mDescriptorPool->obtainSet(layout);
|
||||
auto const& count = layout->count;
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
mResourceAllocator->construct<VulkanDescriptorSet>(handle, mResourceAllocator, vkSet,
|
||||
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||
[vkSet, count, vklayout, this](VulkanDescriptorSet* set) {
|
||||
eraseSetFromHistory(set);
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
});
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::destroySet(Handle<HwDescriptorSet> handle) {
|
||||
mImpl->destroySet(handle);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::initVkLayout(VulkanDescriptorSetLayout* layout) {
|
||||
layout->setVkLayout(mLayoutManager->getVkLayout(layout));
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::eraseSetFromHistory(VulkanDescriptorSet* set) {
|
||||
for (uint8_t i = 0; i < mStashedSets.size(); ++i) {
|
||||
if (mStashedSets[i] == set) {
|
||||
mStashedSets[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to
|
||||
@@ -45,6 +47,7 @@ public:
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
|
||||
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
|
||||
~VulkanDescriptorSetManager();
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
@@ -68,9 +71,29 @@ public:
|
||||
|
||||
void destroySet(Handle<HwDescriptorSet> handle);
|
||||
|
||||
void initVkLayout(VulkanDescriptorSetLayout* layout);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl* mImpl;
|
||||
class DescriptorSetLayoutManager;
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
void eraseSetFromHistory(VulkanDescriptorSet* set);
|
||||
|
||||
using DescriptorSetArray =
|
||||
std::array<VulkanDescriptorSet*, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
VkDevice mDevice;
|
||||
VulkanResourceAllocator* mResourceAllocator;
|
||||
std::unique_ptr<DescriptorSetLayoutManager> mLayoutManager;
|
||||
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
|
||||
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
|
||||
DescriptorSetArray mStashedSets = {};
|
||||
|
||||
struct {
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
DescriptorSetMask setMask;
|
||||
DescriptorSetArray boundSets;
|
||||
} mLastBoundInfo;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -159,6 +159,7 @@ ExtensionSet getInstanceExtensions(ExtensionSet const& externallyRequiredExts =
|
||||
// Request all cross-platform extensions.
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
|
||||
VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME,
|
||||
|
||||
// Request these if available.
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
@@ -300,10 +301,11 @@ 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 +316,29 @@ 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;
|
||||
deviceCreateInfo.queueCreateInfoCount = protectedGraphicsQueueFamilyIndex == INVALID_VK_INDEX ? 1 : 2;
|
||||
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,8 +369,20 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
pNext = &multiview;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceProtectedMemoryFeatures protectedMemory = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
|
||||
};
|
||||
if (protectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX) {
|
||||
// Enable protected memory, if requested.
|
||||
protectedMemory.protectedMemory = VK_TRUE;
|
||||
|
||||
protectedMemory.pNext = pNext;
|
||||
pNext = &protectedMemory;
|
||||
}
|
||||
|
||||
deviceCreateInfo.pNext = pNext;
|
||||
|
||||
|
||||
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, VKALLOC, &device);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateDevice error=" << result << ".";
|
||||
|
||||
@@ -406,13 +427,13 @@ FixedCapacityVector<VkQueueFamilyProperties> getPhysicalDeviceQueueFamilyPropert
|
||||
return queueFamiliesProperties;
|
||||
}
|
||||
|
||||
uint32_t identifyGraphicsQueueFamilyIndex(VkPhysicalDevice physicalDevice) {
|
||||
uint32_t identifyGraphicsQueueFamilyIndex(VkPhysicalDevice physicalDevice, VkQueueFlags flags = VK_QUEUE_GRAPHICS_BIT) {
|
||||
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;
|
||||
}
|
||||
@@ -541,12 +562,16 @@ VkFormatList findAttachmentDepthStencilFormats(VkPhysicalDevice device) {
|
||||
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
|
||||
// VK_FORMAT_D16_UNORM,
|
||||
// VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
};
|
||||
std::vector<VkFormat> selectedFormats;
|
||||
for (VkFormat format: formats) {
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(device, format, &props);
|
||||
if ((props.optimalTilingFeatures & features) == features) {
|
||||
if ((props.optimalTilingFeatures & features) == features ||
|
||||
(props.linearTilingFeatures & features) == features) {
|
||||
selectedFormats.push_back(format);
|
||||
}
|
||||
}
|
||||
@@ -584,6 +609,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,10 +714,24 @@ 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
|
||||
@@ -698,10 +740,20 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
: 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
|
||||
= mImpl->mProtectedGraphicsQueueFamilyIndex == INVALID_VK_INDEX
|
||||
? identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT))
|
||||
: mImpl->mProtectedGraphicsQueueFamilyIndex;
|
||||
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 +763,10 @@ 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,19 +783,42 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
|
||||
mImpl->mDevice
|
||||
= mImpl->mDevice == VK_NULL_HANDLE ? createLogicalDevice(mImpl->mPhysicalDevice,
|
||||
context.mPhysicalDeviceFeatures, mImpl->mGraphicsQueueFamilyIndex, deviceExts)
|
||||
: mImpl->mDevice;
|
||||
context.mPhysicalDeviceFeatures, mImpl->mGraphicsQueueFamilyIndex,
|
||||
mImpl->mProtectedGraphicsQueueFamilyIndex, deviceExts) : mImpl->mDevice;
|
||||
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
|
||||
|
||||
vkGetDeviceQueue(mImpl->mDevice, mImpl->mGraphicsQueueFamilyIndex, mImpl->mGraphicsQueueIndex,
|
||||
&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);
|
||||
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
||||
|
||||
// Check the availability of lazily allocated memory
|
||||
{
|
||||
context.mLazilyAllocatedMemorySupported = false;
|
||||
const uint32_t typeCount = context.mMemoryProperties.memoryTypeCount;
|
||||
for(uint32_t i = 0; i < typeCount; ++i) {
|
||||
const VkMemoryType type = context.mMemoryProperties.memoryTypes[i];
|
||||
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
|
||||
context.mLazilyAllocatedMemorySupported = true;
|
||||
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef NDEBUG
|
||||
// If we are in release build, we should not have turned on debug extensions
|
||||
FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported)
|
||||
@@ -786,6 +865,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, )
|
||||
}
|
||||
@@ -821,6 +904,16 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
|
||||
mImpl->mPhysicalDevice, mImpl->mDevice, mImpl->mGraphicsQueue, mImpl->mInstance,
|
||||
surface, fallbackExtent, flags);
|
||||
mImpl->mSurfaceSwapChains.insert(swapchain);
|
||||
|
||||
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;
|
||||
}
|
||||
else {
|
||||
}
|
||||
}
|
||||
|
||||
return swapchain;
|
||||
}
|
||||
|
||||
@@ -848,6 +941,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
|
||||
|
||||
@@ -28,25 +28,108 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// std::tuple<VkImage, VkDeviceMemory> testing(VulkanContext const& context,
|
||||
// VkDevice device, VkExtent2D extent) {
|
||||
// // Filament expects blit() to work with any texture, so we almost always set these usage flags
|
||||
// // (see copyFrame() and readPixels()).
|
||||
// // The ordering here indicates the preference of choosing depth+stencil format.
|
||||
// VkFormat const formats[] = {
|
||||
// VK_FORMAT_D32_SFLOAT,
|
||||
// VK_FORMAT_X8_D24_UNORM_PACK32,
|
||||
|
||||
// VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
// VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
// VK_FORMAT_D16_UNORM,
|
||||
// VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
|
||||
// VK_FORMAT_R8G8B8A8_SSCALED,
|
||||
// VK_FORMAT_R8G8B8A8_UINT,
|
||||
// VK_FORMAT_R8G8B8A8_SINT,
|
||||
// VK_FORMAT_R8G8B8A8_SRGB,
|
||||
// VK_FORMAT_B8G8R8A8_UNORM,
|
||||
// VK_FORMAT_B8G8R8A8_SNORM,
|
||||
// };
|
||||
|
||||
// uint32_t memoryTypeIndex = VK_MAX_MEMORY_TYPES;
|
||||
// for (auto tiling: {VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_TILING_LINEAR}) {
|
||||
// for (auto format: formats) {
|
||||
// VkImageCreateInfo imageInfo{
|
||||
// .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
// .imageType = VK_IMAGE_TYPE_2D,
|
||||
// .format = format,
|
||||
// .extent = {extent.width, extent.height, 1},
|
||||
// .mipLevels = 1,
|
||||
// .arrayLayers = 1,
|
||||
// .samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
// .tiling = tiling,
|
||||
// .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
|
||||
// };
|
||||
// VkImage image;
|
||||
// VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
|
||||
// FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
// << "Unable to create image: " << static_cast<int32_t>(result);
|
||||
|
||||
// // Allocate memory for the VkImage and bind it.
|
||||
// // VkDeviceMemory imageMemory;
|
||||
// VkMemoryRequirements memReqs;
|
||||
// vkGetImageMemoryRequirements(device, image, &memReqs);
|
||||
|
||||
// utils::slog.e << "tiling=" << tiling << utils::io::endl;
|
||||
// utils::slog.e << "choosing format=" << format << utils::io::endl;
|
||||
// utils::slog.e << "required bits=" << memReqs.memoryTypeBits << utils::io::endl;
|
||||
|
||||
// memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits,
|
||||
// VK_MEMORY_PROPERTY_PROTECTED_BIT);
|
||||
// if (memoryTypeIndex != VK_MAX_MEMORY_TYPES) {
|
||||
// utils::slog.e << "found!" << utils::io::endl;
|
||||
// break;
|
||||
// }
|
||||
// if (memoryTypeIndex != VK_MAX_MEMORY_TYPES) {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
|
||||
// // << "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
|
||||
// // }
|
||||
// // VkMemoryAllocateInfo allocInfo = {
|
||||
// // .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
// // .allocationSize = memReqs.size,
|
||||
// // .memoryTypeIndex = memoryTypeIndex,
|
||||
// // };
|
||||
// // result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
|
||||
// // FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory.";
|
||||
// // result = vkBindImageMemory(device, image, imageMemory, 0);
|
||||
// // FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image.";
|
||||
// return std::tuple(VK_NULL_HANDLE, VK_NULL_HANDLE);
|
||||
// // return std::tuple(image, imageMemory);
|
||||
// }
|
||||
|
||||
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.
|
||||
// TODO: investigate performance implications of setting these flags.
|
||||
VkImageUsageFlags const blittable
|
||||
= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
VkImageCreateInfo imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {extent.width, extent.height, 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = blittable
|
||||
| (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
|
||||
: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
|
||||
// if (isDepth) {
|
||||
// return testing(context, device, extent);
|
||||
// }
|
||||
|
||||
// Filament expects blit() to work with any texture, so we almost always set these usage flags
|
||||
// (see copyFrame() and readPixels()).
|
||||
VkImageUsageFlags const blittable =
|
||||
(isProtected ? 0u
|
||||
: (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT));
|
||||
|
||||
VkImageCreateInfo imageInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {extent.width, extent.height, 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = blittable | (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
|
||||
: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
|
||||
};
|
||||
VkImage image;
|
||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
|
||||
@@ -58,16 +141,19 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetImageMemoryRequirements(device, image, &memReqs);
|
||||
|
||||
uint32_t memoryTypeIndex
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
// uint32_t const memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits,
|
||||
// (isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
|
||||
uint32_t const memoryTypeIndex =
|
||||
context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
// (isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT :
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
|
||||
<< "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
|
||||
|
||||
VkMemoryAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory.";
|
||||
@@ -77,11 +163,19 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
||||
}
|
||||
|
||||
VkFormat selectDepthFormat(VkFormatList const& depthFormats, bool hasStencil) {
|
||||
for (auto f: depthFormats) {
|
||||
utils::slog.e <<"formats=" << f << utils::io::endl;
|
||||
}
|
||||
|
||||
/*
|
||||
auto const formatItr = std::find_if(depthFormats.begin(), depthFormats.end(),
|
||||
hasStencil ? isVkStencilFormat : isVkDepthFormat);
|
||||
assert_invariant(
|
||||
formatItr != depthFormats.end() && "Cannot find suitable swapchain depth format");
|
||||
return *formatItr;
|
||||
*/
|
||||
// return VK_FORMAT_D16_UNORM_S8_UINT;
|
||||
return VK_FORMAT_D16_UNORM;
|
||||
// return *formatItr;
|
||||
}
|
||||
|
||||
}// anonymous namespace
|
||||
@@ -110,8 +204,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 +220,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();
|
||||
}
|
||||
@@ -138,16 +234,32 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
|
||||
|
||||
VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
VkSurfaceFormatKHR surfaceFormat = {};
|
||||
VkSurfaceCapabilitiesKHR caps;
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, mSurface, &caps);
|
||||
VkSurfaceProtectedCapabilitiesKHR protectedCaps = {
|
||||
.sType = VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR,
|
||||
};
|
||||
VkSurfaceCapabilities2KHR caps = {
|
||||
.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
|
||||
.pNext = &protectedCaps,
|
||||
};
|
||||
VkPhysicalDeviceSurfaceInfo2KHR info = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
|
||||
.surface = mSurface,
|
||||
};
|
||||
vkGetPhysicalDeviceSurfaceCapabilities2KHR(mPhysicalDevice, &info, &caps);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!mIsProtected || protectedCaps.supportsProtected == VK_TRUE)
|
||||
<< "Requesting protected swapchain but is not supported by the hardware";
|
||||
|
||||
utils::slog.e << "------ protected surface=" << (protectedCaps.supportsProtected == VK_TRUE)
|
||||
<< utils::io::endl;
|
||||
|
||||
// The general advice is to require one more than the minimum swap chain length, since the
|
||||
// absolute minimum could easily require waiting for a driver or presentation layer to release
|
||||
// the previous frame's buffer. The only situation in which we'd ask for the minimum length is
|
||||
// when using a MAILBOX presentation strategy for low-latency situations where tearing is
|
||||
// acceptable.
|
||||
uint32_t const maxImageCount = caps.maxImageCount;
|
||||
uint32_t const minImageCount = caps.minImageCount;
|
||||
uint32_t const maxImageCount = caps.surfaceCapabilities.maxImageCount;
|
||||
uint32_t const minImageCount = caps.surfaceCapabilities.minImageCount;
|
||||
uint32_t desiredImageCount = minImageCount + 1;
|
||||
|
||||
// According to section 30.5 of VK 1.1, maxImageCount of zero means "that there is no limit on
|
||||
@@ -156,7 +268,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
if (maxImageCount != 0 && desiredImageCount > maxImageCount) {
|
||||
FVK_LOGE << "Swap chain does not support " << desiredImageCount << " images."
|
||||
<< utils::io::endl;
|
||||
desiredImageCount = caps.minImageCount;
|
||||
desiredImageCount = caps.surfaceCapabilities.minImageCount;
|
||||
}
|
||||
|
||||
// Find a suitable surface format.
|
||||
@@ -198,60 +310,68 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
<< "Desired present mode is not supported by this device.";
|
||||
|
||||
// Create the low-level swap chain.
|
||||
if (caps.currentExtent.width == VULKAN_UNDEFINED_EXTENT
|
||||
|| caps.currentExtent.height == VULKAN_UNDEFINED_EXTENT) {
|
||||
auto const& currentExtent = caps.surfaceCapabilities.currentExtent;
|
||||
if (currentExtent.width == VULKAN_UNDEFINED_EXTENT
|
||||
|| currentExtent.height == VULKAN_UNDEFINED_EXTENT) {
|
||||
mSwapChainBundle.extent = mFallbackExtent;
|
||||
} else {
|
||||
mSwapChainBundle.extent = caps.currentExtent;
|
||||
mSwapChainBundle.extent = currentExtent;
|
||||
}
|
||||
|
||||
VkCompositeAlphaFlagBitsKHR const compositeAlpha
|
||||
= (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
|
||||
? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
|
||||
: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
VkCompositeAlphaFlagBitsKHR const compositeAlpha =
|
||||
(caps.surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
|
||||
? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
|
||||
: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
|
||||
VkImageUsageFlags const transferBits =
|
||||
(mIsProtected ? VkImageUsageFlagBits{}
|
||||
: (VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination
|
||||
// (for copyFrame)
|
||||
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT // Allows use as a blit source (for
|
||||
// readPixels)
|
||||
));
|
||||
VkSwapchainCreateInfoKHR const createInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.surface = mSurface,
|
||||
.minImageCount = desiredImageCount,
|
||||
.imageFormat = surfaceFormat.format,
|
||||
.imageColorSpace = surfaceFormat.colorSpace,
|
||||
.imageExtent = mSwapChainBundle.extent,
|
||||
.imageArrayLayers = 1,
|
||||
.imageUsage
|
||||
= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
|
||||
| VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination.
|
||||
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT,// Allows use as a blit source (for readPixels)
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.flags = mIsProtected ? VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR : VkSwapchainCreateFlagsKHR{},
|
||||
.surface = mSurface,
|
||||
.minImageCount = desiredImageCount,
|
||||
.imageFormat = surfaceFormat.format,
|
||||
.imageColorSpace = surfaceFormat.colorSpace,
|
||||
.imageExtent = mSwapChainBundle.extent,
|
||||
.imageArrayLayers = 1,
|
||||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transferBits,
|
||||
|
||||
// TODO: Setting the preTransform to IDENTITY means we are letting the Android
|
||||
// Compositor handle the rotation. In some situations it might be more efficient to
|
||||
// handle this ourselves by setting this field to be equal to the currentTransform mask
|
||||
// in the caps, but this would involve adjusting the MVP, derivatives in GLSL, and
|
||||
// possibly more.
|
||||
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
|
||||
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
|
||||
// TODO: Setting the preTransform to IDENTITY means we are letting the
|
||||
// Android Compositor handle the rotation. In some situations it might be
|
||||
// more efficient to handle this ourselves by setting this field to be equal
|
||||
// to the currentTransform mask in the caps, but this would involve
|
||||
// adjusting the MVP, derivatives in GLSL, and possibly more.
|
||||
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
|
||||
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
|
||||
|
||||
.compositeAlpha = compositeAlpha,
|
||||
.presentMode = desiredPresentMode,
|
||||
.clipped = VK_TRUE,
|
||||
.compositeAlpha = compositeAlpha,
|
||||
.presentMode = desiredPresentMode,
|
||||
.clipped = VK_TRUE,
|
||||
|
||||
.oldSwapchain = mSwapchain,
|
||||
.oldSwapchain = mSwapchain,
|
||||
};
|
||||
VkResult result = vkCreateSwapchainKHR(mDevice, &createInfo, VKALLOC, &mSwapchain);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkGetPhysicalDeviceSurfaceFormatsKHR error: " << static_cast<int32_t>(result);
|
||||
<< "vkCreateSwapchainKHR error: " << static_cast<int32_t>(result);
|
||||
|
||||
mSwapChainBundle.colors = enumerate(vkGetSwapchainImagesKHR, mDevice, mSwapchain);
|
||||
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.protection = 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) << ", "
|
||||
<< "identity-transform=" << (caps.surfaceCapabilities.currentTransform == 1) << ", "
|
||||
<< "depth=" << mSwapChainBundle.depthFormat
|
||||
<< io::endl;
|
||||
|
||||
@@ -317,6 +437,10 @@ bool VulkanPlatformSurfaceSwapChain::hasResized() {
|
||||
return !equivalent(mSwapChainBundle.extent, perceivedExtent);
|
||||
}
|
||||
|
||||
bool VulkanPlatformSurfaceSwapChain::isProtected() {
|
||||
return mIsProtected;
|
||||
}
|
||||
|
||||
// Non-virtual override
|
||||
VkResult VulkanPlatformSurfaceSwapChain::recreate() {
|
||||
destroy();
|
||||
@@ -338,6 +462,10 @@ VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext c
|
||||
VkDevice device, VkQueue queue, VkExtent2D extent, uint64_t flags)
|
||||
: VulkanPlatformSwapChainImpl(context, device, queue),
|
||||
mCurrentIndex(0) {
|
||||
|
||||
assert_invariant(!(flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) &&
|
||||
"Protected headless swapchain not supported");
|
||||
|
||||
mSwapChainBundle.extent = extent;
|
||||
mSwapChainBundle.colorFormat = (flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0
|
||||
? VK_FORMAT_R8G8B8A8_SRGB
|
||||
@@ -347,13 +475,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountA++;
|
||||
});
|
||||
}, 0);
|
||||
|
||||
// Render the first frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
@@ -58,7 +58,7 @@ TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountB++;
|
||||
});
|
||||
}, 0);
|
||||
|
||||
// Render one final frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <private/backend/HandleAllocator.h>
|
||||
#include "utils/Panic.h"
|
||||
|
||||
using namespace filament::backend;
|
||||
|
||||
// FIXME: consider making this constant non-private so we can use it in tests.
|
||||
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u;
|
||||
static constexpr size_t POOL_SIZE_BYTES = 8 * 1024U * 1024U;
|
||||
// NOTE: actual count may be lower due to alignment requirements
|
||||
constexpr size_t const POOL_HANDLE_COUNT = POOL_SIZE_BYTES / (32 + 96 + 136); // 31775
|
||||
|
||||
// This must match HandleAllocatorGL, so its implementation is present on all platforms.
|
||||
#define HandleAllocatorTest HandleAllocator<32, 96, 136> // ~4520 / pool / MiB
|
||||
|
||||
struct MyHandle {
|
||||
};
|
||||
|
||||
struct Concrete : public MyHandle {
|
||||
uint8_t data[32];
|
||||
};
|
||||
|
||||
#if GTEST_HAS_EXCEPTIONS
|
||||
#define EXPECT_THROW_IF_ENABLED EXPECT_THROW
|
||||
#else
|
||||
#define EXPECT_THROW_IF_ENABLED(args...)
|
||||
#endif
|
||||
|
||||
TEST(HandlesTest, useAfterFreePool) {
|
||||
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
|
||||
|
||||
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
|
||||
allocator.deallocate(handleA);
|
||||
|
||||
Handle<MyHandle> handleB = allocator.allocate<Concrete>();
|
||||
|
||||
EXPECT_THROW_IF_ENABLED({
|
||||
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(handleA);
|
||||
}, utils::PostconditionPanic);
|
||||
}
|
||||
|
||||
TEST(HandlesTest, useAfterFreeHeap) {
|
||||
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
|
||||
|
||||
// Use up all the non-heap handles.
|
||||
for (int i = 0; i < POOL_HANDLE_COUNT; i++) {
|
||||
Handle<MyHandle> handle = allocator.allocate<Concrete>();
|
||||
}
|
||||
|
||||
// This one is guaranteed to be a heap handle.
|
||||
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
|
||||
EXPECT_TRUE(handleA.getId() & HANDLE_HEAP_FLAG);
|
||||
allocator.deallocate(handleA);
|
||||
|
||||
Handle<MyHandle> handleB = allocator.allocate<Concrete>();
|
||||
|
||||
// This should assert:
|
||||
EXPECT_THROW_IF_ENABLED({
|
||||
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(handleA);
|
||||
}, utils::PostconditionPanic);
|
||||
|
||||
// simulate a "corrupted" handle
|
||||
Handle<MyHandle> corruptedHandle { HANDLE_HEAP_FLAG | 0x123456 };
|
||||
|
||||
EXPECT_THROW_IF_ENABLED({
|
||||
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(corruptedHandle);
|
||||
}, utils::PostconditionPanic);
|
||||
}
|
||||
|
||||
TEST(HandlesTest, isValid) {
|
||||
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
|
||||
Handle<MyHandle> poolHandle = allocator.allocate<Concrete>();
|
||||
EXPECT_TRUE((poolHandle.getId() & HANDLE_HEAP_FLAG) == 0u);
|
||||
|
||||
// Use up all the non-heap handles.
|
||||
for (int i = 0; i < POOL_HANDLE_COUNT; i++) {
|
||||
Handle<MyHandle> handle = allocator.allocate<Concrete>();
|
||||
}
|
||||
|
||||
// This one is guaranteed to be a heap handle.
|
||||
Handle<MyHandle> heapHandle = allocator.allocate<Concrete>();
|
||||
EXPECT_TRUE(heapHandle.getId() & HANDLE_HEAP_FLAG);
|
||||
|
||||
EXPECT_TRUE(allocator.is_valid(poolHandle));
|
||||
EXPECT_TRUE(allocator.is_valid(heapHandle));
|
||||
|
||||
{
|
||||
// null handles are invalid
|
||||
Handle<MyHandle> handle;
|
||||
EXPECT_FALSE(allocator.is_valid(handle));
|
||||
}
|
||||
{
|
||||
Handle<MyHandle> handle { HANDLE_HEAP_FLAG | 0x123456 };
|
||||
EXPECT_FALSE(allocator.is_valid(handle));
|
||||
}
|
||||
}
|
||||
@@ -288,16 +288,6 @@ public:
|
||||
*/
|
||||
uint32_t jobSystemThreadCount = 0;
|
||||
|
||||
/*
|
||||
* Number of most-recently destroyed textures to track for use-after-free.
|
||||
*
|
||||
* This will cause the backend to throw an exception when a texture is freed but still bound
|
||||
* to a SamplerGroup and used in a draw call. 0 disables completely.
|
||||
*
|
||||
* Currently only respected by the Metal backend.
|
||||
*/
|
||||
size_t textureUseAfterFreePoolSize = 0;
|
||||
|
||||
/**
|
||||
* When uploading vertex or index data, the Filament Metal backend copies data
|
||||
* into a shared staging area before transferring it to the GPU. This setting controls
|
||||
@@ -390,8 +380,14 @@ public:
|
||||
* it's a GLES2 context. Ignored on other backends.
|
||||
*/
|
||||
bool forceGLES2Context = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
|
||||
* This is only supported for:
|
||||
* - PlatformEGLAndroid
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
};
|
||||
|
||||
#if UTILS_HAS_THREADING
|
||||
using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token);
|
||||
|
||||
@@ -118,7 +118,7 @@ public:
|
||||
Builder& mipLevel(AttachmentPoint attachment, uint8_t level) noexcept;
|
||||
|
||||
/**
|
||||
* Sets the cubemap face for a given attachment point.
|
||||
* Sets the face for cubemap textures at the given attachment point.
|
||||
*
|
||||
* @param attachment The attachment point.
|
||||
* @param face The associated cubemap face.
|
||||
@@ -127,7 +127,12 @@ public:
|
||||
Builder& face(AttachmentPoint attachment, CubemapFace face) noexcept;
|
||||
|
||||
/**
|
||||
* Sets the layer for a given attachment point (for 3D textures).
|
||||
* Sets an index of a single layer for 2d array, cubemap array, and 3d textures at the given
|
||||
* attachment point.
|
||||
*
|
||||
* For cubemap array textures, layer is translated into an array index and face according to
|
||||
* - index: layer / 6
|
||||
* - face: layer % 6
|
||||
*
|
||||
* @param attachment The attachment point.
|
||||
* @param layer The associated cubemap layer.
|
||||
@@ -135,6 +140,19 @@ public:
|
||||
*/
|
||||
Builder& layer(AttachmentPoint attachment, uint32_t layer) noexcept;
|
||||
|
||||
/**
|
||||
* Sets the starting index of the 2d array textures for multiview at the given attachment
|
||||
* point.
|
||||
*
|
||||
* This requires COLOR and DEPTH attachments (if set) to be of 2D array textures.
|
||||
*
|
||||
* @param attachment The attachment point.
|
||||
* @param layerCount The number of layers used for multiview, starting from baseLayer.
|
||||
* @param baseLayer The starting index of the 2d array texture.
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
Builder& multiview(AttachmentPoint attachment, uint8_t layerCount, uint8_t baseLayer = 0) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the RenderTarget object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -508,7 +508,7 @@ public:
|
||||
*
|
||||
* Framebuffer as seen on User buffer (PixelBufferDescriptor&)
|
||||
* screen
|
||||
*
|
||||
*
|
||||
* +--------------------+
|
||||
* | | .stride .alignment
|
||||
* | | ----------------------->-->
|
||||
@@ -538,6 +538,9 @@ public:
|
||||
* uploaded to it via setImage, the data returned from readPixels will be y-flipped with respect
|
||||
* to the setImage call.
|
||||
*
|
||||
* Note: the texture that backs the COLOR attachment for `renderTarget` must have
|
||||
* TextureUsage::BLIT_SRC as part of its usage.
|
||||
*
|
||||
* @remark
|
||||
* readPixels() is intended for debugging and testing. It will impact performance significantly.
|
||||
*
|
||||
|
||||
@@ -254,6 +254,19 @@ public:
|
||||
|
||||
void* UTILS_NULLABLE getNativeWindow() const noexcept;
|
||||
|
||||
/**
|
||||
* If this flag is passed to setFrameScheduledCallback, then the behavior of the default
|
||||
* CallbackHandler (when nullptr is passed as the handler argument) is altered to call the
|
||||
* callback on the Metal completion handler thread (as opposed to the main Filament thread).
|
||||
* This flag also instructs the Metal backend to release the associated CAMetalDrawable on the
|
||||
* completion handler thread.
|
||||
*
|
||||
* This flag has no effect if a custom CallbackHandler is passed.
|
||||
*
|
||||
* @see setFrameScheduledCallback
|
||||
*/
|
||||
static constexpr uint64_t CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER = 1;
|
||||
|
||||
/**
|
||||
* FrameScheduledCallback is a callback function that notifies an application when Filament has
|
||||
* completed processing a frame and that frame is ready to be scheduled for presentation.
|
||||
@@ -298,7 +311,7 @@ public:
|
||||
* @see PresentCallable
|
||||
*/
|
||||
void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
|
||||
FrameScheduledCallback&& callback = {});
|
||||
FrameScheduledCallback&& callback = {}, uint64_t flags = 0);
|
||||
|
||||
/**
|
||||
* Returns whether or not this SwapChain currently has a FrameScheduledCallback set.
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -56,7 +56,12 @@ class Bimap {
|
||||
}
|
||||
};
|
||||
|
||||
using ForwardMap = tsl::robin_map<KeyDelegate, Value, KeyHasherDelegate>;
|
||||
using ForwardMap = tsl::robin_map<
|
||||
KeyDelegate, Value,
|
||||
KeyHasherDelegate,
|
||||
std::equal_to<KeyDelegate>,
|
||||
std::allocator<std::pair<KeyDelegate, Value>>,
|
||||
true>;
|
||||
using BackwardMap = tsl::robin_map<Value, KeyDelegate, ValueHash>;
|
||||
|
||||
Allocator mAllocator;
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 "HwDescriptorSetLayoutFactory.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/backend/DriverApi.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
size_t HwDescriptorSetLayoutFactory::Parameters::hash() const noexcept {
|
||||
return utils::hash::murmurSlow(
|
||||
reinterpret_cast<uint8_t const *>(dsl.bindings.data()),
|
||||
dsl.bindings.size() * sizeof(backend::DescriptorSetLayoutBinding),
|
||||
42);
|
||||
}
|
||||
|
||||
bool operator==(HwDescriptorSetLayoutFactory::Parameters const& lhs,
|
||||
HwDescriptorSetLayoutFactory::Parameters const& rhs) noexcept {
|
||||
return (lhs.dsl.bindings.size() == rhs.dsl.bindings.size()) &&
|
||||
std::equal(
|
||||
lhs.dsl.bindings.begin(), lhs.dsl.bindings.end(),
|
||||
rhs.dsl.bindings.begin());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
HwDescriptorSetLayoutFactory::HwDescriptorSetLayoutFactory()
|
||||
: mArena("HwDescriptorSetLayoutFactory::mArena", SET_ARENA_SIZE),
|
||||
mBimap(mArena) {
|
||||
mBimap.reserve(256);
|
||||
}
|
||||
|
||||
HwDescriptorSetLayoutFactory::~HwDescriptorSetLayoutFactory() noexcept = default;
|
||||
|
||||
void HwDescriptorSetLayoutFactory::terminate(DriverApi&) noexcept {
|
||||
assert_invariant(mBimap.empty());
|
||||
}
|
||||
|
||||
auto HwDescriptorSetLayoutFactory::create(DriverApi& driver,
|
||||
backend::DescriptorSetLayout dsl) noexcept -> Handle {
|
||||
|
||||
std::sort(dsl.bindings.begin(), dsl.bindings.end(),
|
||||
[](auto&& lhs, auto&& rhs) {
|
||||
return lhs.binding < rhs.binding;
|
||||
});
|
||||
|
||||
// see if we already have seen this RenderPrimitive
|
||||
Key const key({ dsl });
|
||||
auto pos = mBimap.find(key);
|
||||
|
||||
// the common case is that we've never seen it (i.e.: no reuse)
|
||||
if (UTILS_LIKELY(pos == mBimap.end())) {
|
||||
auto handle = driver.createDescriptorSetLayout(std::move(dsl));
|
||||
mBimap.insert(key, { handle });
|
||||
return handle;
|
||||
}
|
||||
|
||||
++(pos->first.pKey->refs);
|
||||
|
||||
return pos->second.handle;
|
||||
}
|
||||
|
||||
void HwDescriptorSetLayoutFactory::destroy(DriverApi& driver, Handle handle) noexcept {
|
||||
// look for this handle in our map
|
||||
auto pos = mBimap.find(Value{ handle });
|
||||
if (--pos->second.pKey->refs == 0) {
|
||||
mBimap.erase(pos);
|
||||
driver.destroyDescriptorSetLayout(handle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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_HWDESCRIPTORSETLAYOUTFACTORY_H
|
||||
#define TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H
|
||||
|
||||
#include "Bimap.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class FEngine;
|
||||
|
||||
class HwDescriptorSetLayoutFactory {
|
||||
public:
|
||||
using Handle = backend::DescriptorSetLayoutHandle;
|
||||
|
||||
HwDescriptorSetLayoutFactory();
|
||||
~HwDescriptorSetLayoutFactory() noexcept;
|
||||
|
||||
HwDescriptorSetLayoutFactory(HwDescriptorSetLayoutFactory const& rhs) = delete;
|
||||
HwDescriptorSetLayoutFactory(HwDescriptorSetLayoutFactory&& rhs) noexcept = delete;
|
||||
HwDescriptorSetLayoutFactory& operator=(HwDescriptorSetLayoutFactory const& rhs) = delete;
|
||||
HwDescriptorSetLayoutFactory& operator=(HwDescriptorSetLayoutFactory&& rhs) noexcept = delete;
|
||||
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
|
||||
struct Parameters { // 16 bytes + heap allocations
|
||||
backend::DescriptorSetLayout dsl;
|
||||
size_t hash() const noexcept;
|
||||
};
|
||||
|
||||
friend bool operator==(Parameters const& lhs, Parameters const& rhs) noexcept;
|
||||
|
||||
Handle create(backend::DriverApi& driver, backend::DescriptorSetLayout dsl) noexcept;
|
||||
|
||||
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
|
||||
|
||||
private:
|
||||
struct Key { // 24 bytes
|
||||
// The key should not be copyable, unfortunately due to how the Bimap works we have
|
||||
// to copy-construct it once.
|
||||
Key(Key const&) = default;
|
||||
Key& operator=(Key const&) = delete;
|
||||
Key& operator=(Key&&) noexcept = delete;
|
||||
explicit Key(Parameters const& params) : params(params), refs(1) { }
|
||||
Parameters params;
|
||||
mutable uint32_t refs; // 4 bytes
|
||||
bool operator==(Key const& rhs) const noexcept {
|
||||
return params == rhs.params;
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyHasher {
|
||||
size_t operator()(Key const& p) const noexcept {
|
||||
return p.params.hash();
|
||||
}
|
||||
};
|
||||
|
||||
struct Value { // 4 bytes
|
||||
Handle handle;
|
||||
};
|
||||
|
||||
struct ValueHasher {
|
||||
size_t operator()(Value const v) const noexcept {
|
||||
return std::hash<Handle::HandleId>()(v.handle.getId());
|
||||
}
|
||||
};
|
||||
|
||||
friend bool operator==(Value const lhs, Value const rhs) noexcept {
|
||||
return lhs.handle == rhs.handle;
|
||||
}
|
||||
|
||||
// Size of the arena used for the "set" part of the bimap
|
||||
// about ~1K entries before fall back to heap
|
||||
static constexpr size_t SET_ARENA_SIZE = 24 * 1024;
|
||||
|
||||
// Arena for the set<>, using a pool allocator inside a heap area.
|
||||
using PoolAllocatorArena = utils::Arena<
|
||||
utils::PoolAllocatorWithFallback<sizeof(Key)>,
|
||||
utils::LockingPolicy::NoLock,
|
||||
utils::TrackingPolicy::Untracked,
|
||||
utils::AreaPolicy::HeapArea>;
|
||||
|
||||
|
||||
// Arena where the set memory is allocated
|
||||
PoolAllocatorArena mArena;
|
||||
|
||||
// The special Bimap
|
||||
Bimap<Key, Value, KeyHasher, ValueHasher,
|
||||
utils::STLAllocator<Key, PoolAllocatorArena>> mBimap;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
|
||||
struct Parameters { // 20 bytes
|
||||
struct Parameters { // 12 bytes
|
||||
backend::VertexBufferHandle vbh; // 4
|
||||
backend::IndexBufferHandle ibh; // 4
|
||||
backend::PrimitiveType type; // 4
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
|
||||
|
||||
private:
|
||||
struct Key {
|
||||
struct Key { // 16 bytes
|
||||
// The key should not be copyable, unfortunately due to how the Bimap works we have
|
||||
// to copy-construct it once.
|
||||
Key(Key const&) = default;
|
||||
@@ -100,7 +100,8 @@ private:
|
||||
}
|
||||
|
||||
// Size of the arena used for the "set" part of the bimap
|
||||
static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024;
|
||||
// about ~65K entry before fall back to heap
|
||||
static constexpr size_t SET_ARENA_SIZE = 1 * 1024 * 1024;
|
||||
|
||||
// Arena for the set<>, using a pool allocator inside a heap area.
|
||||
using PoolAllocatorArena = utils::Arena<
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
|
||||
struct Parameters { // 136 bytes
|
||||
struct Parameters { // 132 bytes
|
||||
uint8_t bufferCount;
|
||||
uint8_t attributeCount;
|
||||
uint8_t padding[2] = {};
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
|
||||
|
||||
private:
|
||||
struct Key { // 140 bytes
|
||||
struct Key { // 136 bytes
|
||||
// The key should not be copyable, unfortunately due to how the Bimap works we have
|
||||
// to copy-construct it once.
|
||||
Key(Key const&) = default;
|
||||
@@ -101,7 +101,8 @@ private:
|
||||
}
|
||||
|
||||
// Size of the arena used for the "set" part of the bimap
|
||||
static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024;
|
||||
// about ~15K entry before fall back to heap
|
||||
static constexpr size_t SET_ARENA_SIZE = 2 * 1024 * 1024;
|
||||
|
||||
// Arena for the set<>, using a pool allocator inside a heap area.
|
||||
using PoolAllocatorArena = utils::Arena<
|
||||
|
||||
+315
-241
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 {
|
||||
@@ -103,14 +105,6 @@ public:
|
||||
void init() noexcept;
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
|
||||
void bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept {
|
||||
assert_invariant(mFrameUniforms);
|
||||
if (mFrameUniforms) {
|
||||
mPostProcessDescriptorSet.setFrameUniforms(driver, *mFrameUniforms);
|
||||
mPostProcessDescriptorSet.bind(driver);
|
||||
}
|
||||
}
|
||||
|
||||
void configureTemporalAntiAliasingMaterial(
|
||||
TemporalAntiAliasingOptions const& taaOptions) noexcept;
|
||||
|
||||
@@ -328,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;
|
||||
@@ -337,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;
|
||||
@@ -354,43 +353,50 @@ 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,
|
||||
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 commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material,
|
||||
void renderFullScreenQuadWithScissor(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline,
|
||||
backend::Viewport scissor,
|
||||
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(TypedUniformBuffer<PerViewUib>& uniforms) noexcept {
|
||||
mFrameUniforms = std::addressof(uniforms);
|
||||
}
|
||||
// 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;
|
||||
mutable PostProcessDescriptorSet mPostProcessDescriptorSet;
|
||||
TypedUniformBuffer<PerViewUib>* mFrameUniforms = nullptr;
|
||||
|
||||
struct BilateralPassConfig {
|
||||
uint8_t kernelSize = 11;
|
||||
|
||||
+93
-77
@@ -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,
|
||||
@@ -655,12 +644,18 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
bool const hasSkinning = soaVisibility[i].skinning;
|
||||
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
|
||||
|
||||
// if we are already an SSR variant, the SRE bit is already set,
|
||||
// there is no harm setting it again
|
||||
// if we are already an SSR variant, the SRE bit is already set
|
||||
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
|
||||
Variant renderableVariant = variant;
|
||||
renderableVariant.setShadowReceiver(
|
||||
Variant::isSSRVariant(variant) || (soaVisibility[i].receiveShadows & hasShadowing));
|
||||
Variant renderableVariant{ variant };
|
||||
|
||||
// we can't have SSR and shadowing together by construction
|
||||
bool const isSsrVariant = Variant::isSSRVariant(variant);
|
||||
assert_invariant((isSsrVariant && !hasShadowing) || !isSsrVariant);
|
||||
if (!isSsrVariant) {
|
||||
// set the SRE variant, unless we're in SSR mode
|
||||
renderableVariant.setShadowReceiver(soaVisibility[i].receiveShadows && hasShadowing);
|
||||
}
|
||||
|
||||
renderableVariant.setSkinning(hasSkinningOrMorphing);
|
||||
|
||||
FRenderableManager::SkinningBindingInfo const& skinning = soaSkinning[i];
|
||||
@@ -811,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;
|
||||
@@ -853,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
|
||||
@@ -875,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);
|
||||
@@ -891,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);
|
||||
}
|
||||
|
||||
@@ -929,15 +923,17 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
|
||||
// Maximum space occupied in the CircularBuffer by a single `Command`. This must be
|
||||
// reevaluated when the inner loop below adds DriverApi commands or when we change the
|
||||
// CommandStream protocol. Currently, the maximum is 320 bytes.
|
||||
// CommandStream protocol. Currently, the maximum is 248 bytes.
|
||||
// The batch size is calculated by adding the size of all commands that can possibly be
|
||||
// emitted per draw call:
|
||||
constexpr size_t const maxCommandSizeInBytes =
|
||||
sizeof(CustomCommand) +
|
||||
sizeof(COMMAND_TYPE(scissor)) +
|
||||
sizeof(COMMAND_TYPE(bindDescriptorSet)) +
|
||||
sizeof(COMMAND_TYPE(bindDescriptorSet)) +
|
||||
sizeof(COMMAND_TYPE(bindPipeline)) +
|
||||
sizeof(COMMAND_TYPE(setPushConstant)) +
|
||||
sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
|
||||
sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 8) +
|
||||
sizeof(COMMAND_TYPE(setPushConstant)) +
|
||||
sizeof(COMMAND_TYPE(draw2));
|
||||
|
||||
|
||||
@@ -956,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--;
|
||||
@@ -989,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)) {
|
||||
@@ -1017,18 +1022,25 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] =
|
||||
ma->getDescriptorSetLayout().getHandle();
|
||||
|
||||
// If we have a ColorPassDescriptorSet we use it to bind the per-view
|
||||
// descriptor-set (ideally only if it changed). If we don't, it means
|
||||
// the descriptor-set is already bound and the layout we got from the
|
||||
// material above should match. This is the case for situations where we
|
||||
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and
|
||||
// structure passes.
|
||||
|
||||
if (mColorPassDescriptorSet) {
|
||||
// We have a ColorPassDescriptorSet, we need to go through it for binding
|
||||
// the per-view descriptor-set because its layout can change based on the
|
||||
// material.
|
||||
mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex());
|
||||
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
|
||||
// It is possible to get a post-process material here (even though it's
|
||||
// not technically a public API yet, it is used by the IBLPrefilterLibrary.
|
||||
// Ideally we would have a more formal compute API). In this case, we need
|
||||
// to set the post-process descriptor-set.
|
||||
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
|
||||
} else {
|
||||
// If we have a ColorPassDescriptorSet we use it to bind the per-view
|
||||
// descriptor-set (ideally only if it changed). If we don't, it means
|
||||
// the descriptor-set is already bound and the layout we got from the
|
||||
// material above should match. This is the case for situations where we
|
||||
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and
|
||||
// structure passes.
|
||||
if (mColorPassDescriptorSet) {
|
||||
// We have a ColorPassDescriptorSet, we need to go through it for binding
|
||||
// the per-view descriptor-set because its layout can change based on the
|
||||
// material.
|
||||
mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex());
|
||||
}
|
||||
}
|
||||
|
||||
// Each MaterialInstance has its own descriptor set. This binds it.
|
||||
@@ -1070,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1083,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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user