Compare commits

..
Author SHA1 Message Date
Benjamin Doherty 69d5ac2107 Check for invalid texture inside MaterialInstance 2024-09-12 12:11:01 -07:00
Powei Feng c01d2c09b6 vk: reduce map look-up in descriptor set hot loop
DescriptorSetManager's commit() is the inner most of the
rendering loop. We want to avoid any hash map look up here.
We just store a pointer to the history object where appropriate.
2024-09-12 08:48:25 -07:00
Powei Feng f37d8b2fc2 vk: refactor descriptor bitmask to use bitset
Using bitset enables simpler counting of bits and the use of the
handy 'forEachSetBit'.

Also cleaned up various unused code.
2024-09-12 08:48:25 -07:00
Sungun Park 6397fa3fe1 Fix a crash for color grading (#8118)
Set postprocess descriptor set for color grading
2024-09-11 18:51:15 -07:00
Benjamin Doherty 37d1baf0e8 Metal debug logging improvements 2024-09-10 13:36:45 -07:00
Powei Feng 1596a5ec71 vk: Fix descriptor set bitmask padding
The sizing of each field in Bitmask has changed, and the
description was no longer accurate.  This caused msan to fail
because the mask is also used as a key to cache.
2024-09-10 12:13:24 -07:00
Mathias Agopian 22e62b92a6 disable compute test to fix build
this test is no important for now, since we don't have a finished
compute api.

the test breaks because it still use the old non-descriptor based API.
2024-09-09 23:09:01 -07:00
Mathias Agopian 8f979dacaf Merge branch 'main' into ma/descriptor-sets 2024-09-09 22:58:33 -07:00
Benjamin Doherty e3cb67045b Remove some more magic numbers 2024-09-09 17:01:53 -07:00
Benjamin Doherty f4cd9f712a GLSLPostProcessor clean up, remove magic numbers 2024-09-09 16:51:11 -07:00
Benjamin Doherty 99b3587dd3 Update comment 2024-09-09 12:28:35 -07:00
Benjamin Doherty 8e30b1bfc9 Rename toString 2024-09-09 12:26:53 -07:00
Benjamin Doherty c0b1daf339 MetalDescriptorSet move implementation into MetalHandles.mm 2024-09-09 12:23:45 -07:00
Benjamin Doherty efb94da111 Remove deprecated driver APIS 2024-09-09 12:17:27 -07:00
Benjamin Doherty c538bded6f Get rid of unused MetalSamplerGroup and related code 2024-09-09 12:17:27 -07:00
Benjamin Doherty f6d9db14c3 Better DEBUG_LOG 2024-09-09 12:17:27 -07:00
Benjamin Doherty 91ce3a9375 Smaller empty buffer 2024-09-09 12:17:27 -07:00
Mathias Agopian 3aeee1630a Merge branch 'main' into ma/descriptor-sets 2024-09-06 18:49:04 -07:00
Benjamin Doherty 15d77ed1d7 Metal: respect the shader stage for each descriptor 2024-09-05 11:38:10 -07:00
Mathias Agopian 6a64cdb059 fix build due to a bad merge 2024-09-03 12:14:10 -07:00
Mathias Agopian cf0cb66b28 Merge branch 'main' into ma/descriptor-sets 2024-09-03 11:25:40 -07:00
Mathias Agopian 3a2333d709 Merge branch 'main' into ma/descriptor-sets 2024-08-30 16:31:23 -07:00
Mathias Agopian 6790a1238b fix webgl build 2024-08-30 16:28:14 -07:00
Powei Feng fad5d57053 vk: properly compose swizzle 2024-08-30 15:17:03 -07:00
Powei Feng e95f0ed6a0 vk: implement createTextureViewSwizzled() 2024-08-30 15:17:03 -07:00
Mathias Agopian 73b0be799c Merge branch 'main' into ma/descriptor-sets 2024-08-30 15:13:43 -07:00
Mathias Agopian 6de6a2123d implement createTextureViewSwizzled() in the GL backend 2024-08-28 12:53:50 -07:00
Ben Doherty d161ef24f5 Update createTextureView API to include swizzling, implement on Metal (#8077) 2024-08-28 11:52:49 -07:00
Mathias Agopian c3ea1e45f9 Merge branch 'main' into ma/descriptor-sets 2024-08-28 11:45:02 -07:00
Mathias Agopian c6c20df474 Merge branch 'main' into ma/descriptor-sets 2024-08-23 23:04:57 -07:00
Ben Doherty bbb45218ac Fix, assertion when an external image is used but not yet set (#8073) 2024-08-21 15:47:07 -07:00
Mathias Agopian 02bdddf50e fix a too aggressive assert 2024-08-21 15:19:56 -07:00
Powei Feng 21d93ce49d Fix missing duplication of descriptor set in material instance (#8068) 2024-08-21 12:07:24 -07:00
Powei Feng cab5fc561c GLDescriptorSet: less restrictive size assert (#8070)
For some platform, the compilation fails because the size of the
struct is not exactly 32 bits (but is always less).
2024-08-21 11:51:59 -07:00
Mathias Agopian c780804fd6 Merge branch 'main' into ma/descriptor-sets 2024-08-21 10:27:31 -07:00
Mathias Agopian b6d6f8f6e4 Merge branch 'main' into ma/descriptor-sets 2024-08-20 12:08:32 -07:00
Mathias Agopian f098d08c48 Merge branch 'main' into ma/descriptor-sets 2024-08-19 17:13:44 -07:00
Mathias Agopian 5c08f34e8c Merge branch 'main' into ma/descriptor-sets 2024-08-19 16:40:36 -07:00
Mathias Agopian a37d03101e Merge branch 'main' into ma/descriptor-sets 2024-08-19 11:47:19 -07:00
Mathias Agopian c0819b781a Merge branch 'main' into ma/descriptor-sets 2024-08-16 16:03:54 -07:00
Powei Feng d9bf68ed06 vk: fix missing read-after-write barrier (#8046)
We relied on layout transition to emit a barrier, but sometimes
it's skipped. In that case, we explicitly emit a barrier for the
transition from attachment -> sampler.

Without this, we'll hit read-after-write sync validation error
and produce various artifacts (e.g a fade to white effect) on
different platforms.
2024-08-16 15:59:38 -07:00
Powei Feng 32ac10161b Move morphing buffer offset out of pipeline check (#8047)
The push constant call should not be tied to the caching of the
pipeline.
2024-08-16 13:17:37 -07:00
Benjamin Doherty d073fb5342 Implement external images / descriptor sets for Metal 2024-08-16 13:08:24 -07:00
Mathias Agopian 8fe7c1ff30 Merge branch 'main' into ma/descriptor-sets 2024-08-14 22:47:16 -07:00
Mathias Agopian 3b12e54573 Merge branch 'main' into ma/descriptor-sets 2024-08-12 15:09:26 -07:00
Mathias Agopian a1f146ebea Merge branch 'main' into ma/descriptor-sets 2024-08-12 15:05:43 -07:00
Mathias Agopian 9401d6be9e fix warning about missing case
this is to allow -Werror to work.
2024-08-11 23:53:16 -07:00
Mathias Agopian 111be6ef6d Merge branch 'main' into ma/descriptor-sets 2024-08-09 15:25:58 -07:00
Mathias Agopian bd0ca7a9bd Merge branch 'main' into ma/descriptor-sets 2024-08-08 22:48:25 -07:00
Mathias Agopian 8797f45bd0 Merge branch 'main' into ma/descriptor-sets 2024-08-08 13:27:20 -07:00
Mathias Agopian f04a25028e Merge branch 'main' into ma/descriptor-sets 2024-08-01 22:19:16 -07:00
Powei Feng bb1f58e97c Fix broken merge for vk (#8007) 2024-07-31 10:40:09 +08:00
Powei Feng 189e36d539 Merge branch 'main' into ma/descriptor-sets 2024-07-31 10:11:51 +08:00
Mathias Agopian cba819be8f Merge branch 'main' into ma/descriptor-sets 2024-07-25 17:25:08 -07:00
Mathias Agopian 41e1e7e54b Merge branch 'main' into ma/descriptor-sets 2024-07-24 15:38:12 -07:00
Ben Doherty d8580d93ad New external image API (#7981) 2024-07-23 13:06:48 -06:00
Mathias Agopian fef40a8c5d Merge branch 'main' into ma/descriptor-sets 2024-07-19 10:09:28 -07:00
Mathias Agopian d299b1f00e Merge branch 'main' into ma/descriptor-sets 2024-06-28 13:20:28 -07:00
Mathias Agopian daa2790c4e Merge branch 'main' into ma/descriptor-sets 2024-06-28 12:54:46 -07:00
Mathias Agopian d2cb53e39d add minimal support for Texture Views in the backends
Texture Views are currently limited to the min/max lod, and it's not
allowed to have several views of the same texture in a single shader.
Only SAMPLEABLE textures can have a texture view.

Additionally, setMinMaxLevels() is removed as well as all automatic
min/max lods adjustments. This is all handled by filament itself
from now on, using Texture Views.
2024-06-28 12:23:58 -07:00
Mathias Agopian 315c8d75bc vk: repair transmission + MSAA
That code was changed as part as what looked like a refactoring
unrelated to the descriptor-set change itself.
Reverting that thunk fixes the problem, however the revert doesn't 
have the layout transitions that were added.

This PR reverts back to the old behavior, but adds the layout 
transitions.

It looks like the refactored code didn't handle "auto resolve" anymore.
2024-06-28 12:12:27 -07:00
Ben Doherty c19785b72f Merge branch 'main' into ma/descriptor-sets 2024-06-27 17:48:34 -07:00
Benjamin Doherty 38b3074835 Fix validation error, bind descriptor sets at bind time 2024-06-27 17:42:24 -07:00
Benjamin Doherty 929ad27606 Pass the correct stage flags 2024-06-27 14:44:59 -07:00
Benjamin Doherty c12bcbb21e Bind the actual resource at updateDescriptorSet time 2024-06-27 14:44:57 -07:00
Benjamin Doherty 07f07e0700 Remove log spew 2024-06-27 14:35:20 -07:00
Benjamin Doherty 7e0181b2cf Merge remote-tracking branch 'origin/main' into ma/descriptor-sets 2024-06-25 18:19:39 -07:00
Mathias Agopian b4162eb313 fix windows build 2024-06-24 22:57:09 -07:00
Mathias Agopian a0dc7104b6 fix backend tests
The pipeline layout wasn't set properly
2024-06-24 22:08:02 -07:00
Mathias Agopian 80f151ccf7 Merge branch 'main' into ma/descriptor-sets 2024-06-24 16:32:18 -07:00
Benjamin Doherty 26bb99c64e Pass descriptor set info to spirvToMsl more concicely 2024-06-24 15:19:09 -07:00
Benjamin Doherty 1612cdb353 WIP: Implement Metal descriptor sets 2024-06-21 17:00:47 -07:00
Benjamin Doherty e75882e4d4 Update MetalArgumentBuffer to support buffers 2024-06-21 16:42:20 -07:00
Benjamin Doherty 2e763149e7 MetalArgumentBuffer support buffers 2024-06-21 16:42:19 -07:00
Mathias Agopian e40a4c69a3 Merge branch 'main' into ma/descriptor-sets 2024-06-21 09:50:29 -07:00
Mathias Agopian 9a1ee1ff67 fix bad merge 2024-06-18 14:11:04 -07:00
Mathias Agopian 9fb692b2f6 Merge branch 'main' into ma/descriptor-sets 2024-06-18 13:39:02 -07:00
Ben Doherty ac7c679783 Merge branch 'main' into ma/descriptor-sets 2024-06-18 13:21:36 -07:00
Mathias Agopian 38a3c52629 vk: build new descriptor set framework 2024-06-17 23:19:24 -07:00
Mathias Agopian 29ecfd5fda generate the proper per-view descriptor-set layout in the shader
the per-view descriptor-set layout depends on both the material
and the variant.
2024-06-17 23:19:24 -07:00
Mathias Agopian f11a04722b new descriptor set api 2024-06-17 23:19:24 -07:00
213 changed files with 3945 additions and 6991 deletions
@@ -1,9 +0,0 @@
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
+26 -6
View File
@@ -8,15 +8,35 @@ on:
- rc/**
jobs:
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
build-android-armv7:
name: build-android-armv7
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run Android Continuous
uses: ./.github/actions/android-continuous
with:
build-abi: armeabi-v7a,arm64-v8a,x86_64
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
+2 -2
View File
@@ -10,14 +10,14 @@ on:
jobs:
build-ios:
name: build-ios
runs-on: macos-14-xlarge
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-ios
path: out/filament-release-ios.tgz
+2 -2
View File
@@ -10,14 +10,14 @@ on:
jobs:
build-linux:
name: build-linux
runs-on: ubuntu-22.04-16core
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-linux
path: out/filament-release-linux.tgz
+2 -2
View File
@@ -10,14 +10,14 @@ on:
jobs:
build-mac:
name: build-mac
runs-on: macos-14-xlarge
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-mac
path: out/filament-release-darwin.tgz
+5 -20
View File
@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [macos-14-xlarge, ubuntu-22.04-16core]
os: [macos-14, ubuntu-22.04]
steps:
- uses: actions/checkout@v4.1.6
@@ -29,7 +29,7 @@ jobs:
build-windows:
name: build-windows
runs-on: win-2019-16core
runs-on: windows-2019
steps:
- uses: actions/checkout@v4.1.6
@@ -40,7 +40,7 @@ jobs:
build-android:
name: build-android
runs-on: ubuntu-22.04-16core
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
@@ -56,7 +56,7 @@ jobs:
build-ios:
name: build-iOS
runs-on: macos-14-xlarge
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
@@ -69,25 +69,10 @@ jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: macos-14
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
+5 -6
View File
@@ -31,7 +31,7 @@ jobs:
strategy:
matrix:
os: [macos-14-xlarge, ubuntu-22.04-32core]
os: [macos-14, ubuntu-22.04]
steps:
- name: Decide Git ref
@@ -65,7 +65,7 @@ jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: macos-14
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
steps:
@@ -98,7 +98,7 @@ jobs:
build-android:
name: build-android
runs-on: ubuntu-22.04-16core
runs-on: macos-14
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-xlarge
runs-on: macos-14
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-32core
runs-on: windows-2019
if: github.event_name == 'release' || github.event.inputs.platform == 'windows'
steps:
@@ -205,7 +205,6 @@ 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
+2 -2
View File
@@ -10,14 +10,14 @@ on:
jobs:
build-web:
name: build-web
runs-on: ubuntu-22.04-16core
runs-on: macos-14
steps:
- uses: actions/checkout@v4.1.6
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-web
path: out/filament-release-web.tgz
+2 -2
View File
@@ -10,7 +10,7 @@ on:
jobs:
build-windows:
name: build-windows
runs-on: windows-2019-32core
runs-on: windows-2019
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@v4
- uses: actions/upload-artifact@v1.0.0
with:
name: filament-windows
path: out/filament-windows.tgz
-16
View File
@@ -45,8 +45,6 @@ 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."
)
@@ -75,10 +73,6 @@ 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)
@@ -138,22 +132,12 @@ 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)
+2
View File
@@ -7,3 +7,5 @@ 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.
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.56.3'
implementation 'com.google.android.filament:filament-android:1.54.2'
}
```
@@ -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.56.3'
pod 'Filament', '~> 1.54.2'
```
## Documentation
+1 -27
View File
@@ -7,32 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.56.3
## v1.56.2
- vk: fix stage pool gc logic
## v1.56.1
## v1.56.0
- backend: descriptor layouts distinguish samplers and external samplers (b/376089915) [⚠️ **New Material Version**]
## v1.55.1
## 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
@@ -667,7 +641,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 passed to `Stream::SetAcquiredImage` is ignored and leaked.
- engine: Fix, first imaged passsed 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.
+5 -5
View File
@@ -83,12 +83,12 @@ buildscript {
'minSdk': 21,
'targetSdk': 34,
'compileSdk': 34,
'kotlin': '2.0.21',
'kotlin_coroutines': '1.9.0',
'buildTools': '35.0.0',
'kotlin': '2.0.0',
'kotlin_coroutines': '1.9.0-RC',
'buildTools': '34.0.0',
'ndk': '27.0.11718014',
'androidx_core': '1.13.1',
'androidx_annotations': '1.9.0'
'androidx_annotations': '1.8.0'
]
ext.deps = [
@@ -104,7 +104,7 @@ buildscript {
]
dependencies {
classpath 'com.android.tools.build:gradle:8.6.1'
classpath 'com.android.tools.build:gradle:8.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
}
@@ -499,37 +499,6 @@ Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass,
return (jint)engine->getActiveFeatureLevel();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nHasFeatureFlag(JNIEnv *env, jclass clazz,
jlong nativeEngine, jstring name_) {
Engine* engine = (Engine*) nativeEngine;
const char *name = env->GetStringUTFChars(name_, 0);
std::optional<bool> result = engine->getFeatureFlag(name);
env->ReleaseStringUTFChars(name_, name);
return result.has_value();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nSetFeatureFlag(JNIEnv *env, jclass clazz,
jlong nativeEngine, jstring name_, jboolean value) {
Engine* engine = (Engine*) nativeEngine;
const char *name = env->GetStringUTFChars(name_, 0);
jboolean result = engine->setFeatureFlag(name, (bool)value);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nGetFeatureFlag(JNIEnv *env, jclass clazz,
jlong nativeEngine, jstring name_) {
Engine* engine = (Engine*) nativeEngine;
const char *name = env->GetStringUTFChars(name_, 0);
std::optional<bool> result = engine->getFeatureFlag(name);
env->ReleaseStringUTFChars(name_, name);
return result.value_or(false); // we should never fail here
}
extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nCreateBuilder(JNIEnv*,
jclass) {
Engine::Builder* builder = new Engine::Builder{};
@@ -551,12 +520,13 @@ 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, jboolean disableParallelShaderCompile,
jlong jobSystemThreadCount,
jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile,
jint stereoscopicType, jlong stereoscopicEyeCount,
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
jboolean forceGLES2Context) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -565,6 +535,7 @@ 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,
@@ -573,7 +544,6 @@ 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);
}
@@ -596,16 +566,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
builder->paused((bool) paused);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Engine_nSetBuilderFeature(JNIEnv *env, jclass clazz,
jlong nativeBuilder, jstring name_, jboolean value) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
const char *name = env->GetStringUTFChars(name_, 0);
builder->feature(name, (bool)value);
env->ReleaseStringUTFChars(name_, name);
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
@@ -222,20 +222,6 @@ 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,12 +224,13 @@ public class Engine {
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
config.jobSystemThreadCount, config.disableParallelShaderCompile,
config.jobSystemThreadCount,
config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile,
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context, config.assertNativeWindowIsValid);
config.forceGLES2Context);
return this;
}
@@ -258,17 +259,6 @@ public class Engine {
return this;
}
/**
* Set a feature flag value. This is the only way to set constant feature flags.
* @param name feature name
* @param value true to enable, false to disable
* @return A reference to this Builder for chaining calls.
*/
public Builder feature(@NonNull String name, boolean value) {
nSetBuilderFeature(mNativeBuilder, name, value);
return this;
}
/**
* Creates an instance of Engine
*
@@ -404,7 +394,6 @@ public class Engine {
/**
* Set to `true` to forcibly disable parallel shader compilation in the backend.
* Currently only honored by the GL backend.
* @Deprecated use "backend.disable_parallel_shader_compile" feature flag instead
*/
public boolean disableParallelShaderCompile = false;
@@ -430,12 +419,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.
@@ -443,13 +432,12 @@ public class Engine {
*/
public long resourceAllocatorCacheMaxAge = 1;
/**
/*
* Disable backend handles use-after-free checks.
* @Deprecated use "backend.disable_handle_use_after_free_check" feature flag instead
*/
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
@@ -471,20 +459,12 @@ 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
* @Deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
*/
public boolean assertNativeWindowIsValid = false;
}
private Engine(long nativeEngine, Config config) {
@@ -707,11 +687,11 @@ public class Engine {
/**
* Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
* eyes rendered is set at Engine creation time with the {@link Config#stereoscopicEyeCount}
* setting.
* eyes rendered is set at Engine creation time with the {@link
* Engine#Config#stereoscopicEyeCount} setting.
*
* @return the max number of stereoscopic eyes supported
* @see Config#stereoscopicEyeCount
* @see Engine#Config#stereoscopicEyeCount
*/
public long getMaxStereoscopicEyes() {
return nGetMaxStereoscopicEyes(getNativeObject());
@@ -908,8 +888,7 @@ public class Engine {
/**
* Returns whether the object is valid.
* @param ma Material
* @param mi MaterialInstance to check for validity
* @param object Object to check for validity
* @return returns true if the specified object is valid.
*/
public boolean isValidMaterialInstance(@NonNull Material ma, MaterialInstance mi) {
@@ -1331,39 +1310,6 @@ public class Engine {
*/
public static native long getSteadyClockTimeNano();
/**
* Checks if a feature flag exists
* @param name name of the feature flag to check
* @return true if it exists false otherwise
*/
public boolean hasFeatureFlag(@NonNull String name) {
return nHasFeatureFlag(mNativeObject, name);
}
/**
* Set the value of a non-constant feature flag.
* @param name name of the feature flag to set
* @param value value to set
* @return true if the value was set, false if the feature flag is constant or doesn't exist.
*/
public boolean setFeatureFlag(@NonNull String name, boolean value) {
return nSetFeatureFlag(mNativeObject, name, value);
}
/**
* Retrieves the value of any feature flag.
* @param name name of the feature flag
* @return the value of the flag if it exists
* @exception IllegalArgumentException is thrown if the feature flag doesn't exist
*/
public boolean getFeatureFlag(@NonNull String name) {
if (!hasFeatureFlag(name)) {
throw new IllegalArgumentException("The feature flag \"" + name + "\" doesn't exist");
}
return nGetFeatureFlag(mNativeObject, name);
}
@UsedByReflection("TextureHelper.java")
public long getNativeObject() {
if (mNativeObject == 0) {
@@ -1453,9 +1399,6 @@ public class Engine {
private static native int nGetSupportedFeatureLevel(long nativeEngine);
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
private static native int nGetActiveFeatureLevel(long nativeEngine);
private static native boolean nHasFeatureFlag(long nativeEngine, String name);
private static native boolean nSetFeatureFlag(long nativeEngine, String name, boolean value);
private static native boolean nGetFeatureFlag(long nativeEngine, String name);
private static native long nCreateBuilder();
private static native void nDestroyBuilder(long nativeBuilder);
@@ -1463,14 +1406,14 @@ public class Engine {
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
boolean disableParallelShaderCompile, int stereoscopicType, long stereoscopicEyeCount,
long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile,
int stereoscopicType, long stereoscopicEyeCount,
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
boolean forceGLES2Context);
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
private static native void nSetBuilderFeature(long nativeBuilder, String name, boolean value);
private static native long nBuilderBuild(long nativeBuilder);
}
@@ -745,33 +745,6 @@ 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.
*
@@ -1308,8 +1281,6 @@ 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 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.56.3
VERSION_NAME=1.54.2
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+1 -1
View File
@@ -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.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -6,10 +6,6 @@ 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")
@@ -17,7 +13,7 @@ filamentTools {
}
// don't forget to update MainACtivity.kt when/if changing this.
tasks.register('copyMesh', Copy) {
task copyMesh(type: Copy) {
from "../../../third_party/models/BusterDrone"
into "src/main/assets/models"
}
@@ -112,13 +112,7 @@ class MainActivity : Activity() {
}
private fun setupFilament() {
val config = Engine.Config()
//config.forceGLES2Context = true
engine = Engine.Builder()
.config(config)
.featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0)
.build()
engine = Engine.Builder().featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build()
renderer = engine.createRenderer()
scene = engine.createScene()
view = engine.createView()
@@ -129,9 +123,7 @@ class MainActivity : Activity() {
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
// post-processing is not supported at feature level 0
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
view.isPostProcessingEnabled = false
}
view.isPostProcessingEnabled = false
// Tell the view which camera we want to use
view.camera = camera
+1 -9
View File
@@ -64,9 +64,6 @@ 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."
@@ -183,8 +180,6 @@ BACKEND_DEBUG_FLAG_OPTION=""
STEREOSCOPIC_OPTION=""
OSMESA_OPTION=""
IOS_BUILD_SIMULATOR=false
BUILD_UNIVERSAL_LIBRARIES=false
@@ -245,7 +240,6 @@ 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" \
@@ -802,7 +796,7 @@ function check_debug_release_build {
pushd "$(dirname "$0")" > /dev/null
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:X:" opt; do
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
case ${opt} in
h)
print_help
@@ -956,8 +950,6 @@ while getopts ":hacCfgijmp:q:uvslwedk:bx:S:X:" opt; do
exit 1
esac
;;
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
echo ""
+5 -1
View File
@@ -1,6 +1,7 @@
#!/bin/bash
if [ `uname` == "Linux" ];then
source `dirname $0`/../linux/ci-common.sh
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-linux.zip
unzip -q ninja-linux.zip
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
@@ -12,6 +13,9 @@ 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
+8 -14
View File
@@ -115,23 +115,17 @@ 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.
:: 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
@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 . %INSTALL% --config %config% %build_flags% -- /m || exit /b
cmake --build . %INSTALL% --config %config% -- /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.
-14
View File
@@ -66,7 +66,6 @@ set(SRCS
src/FrameSkipper.cpp
src/Froxelizer.cpp
src/Frustum.cpp
src/HwDescriptorSetLayoutFactory.cpp
src/HwRenderPrimitiveFactory.cpp
src/HwVertexBufferInfoFactory.cpp
src/IndexBuffer.cpp
@@ -154,7 +153,6 @@ set(PRIVATE_HDRS
src/FrameInfo.h
src/FrameSkipper.h
src/Froxelizer.h
src/HwDescriptorSetLayoutFactory.h
src/HwRenderPrimitiveFactory.h
src/HwVertexBufferInfoFactory.h
src/Intersections.h
@@ -308,11 +306,6 @@ if (FILAMENT_ENABLE_MULTIVIEW)
add_definitions(-DFILAMENT_ENABLE_MULTIVIEW)
endif()
# Whether to force the profiling mode.
if (FILAMENT_FORCE_PROFILING_MODE)
add_definitions(-DFILAMENT_FORCE_PROFILING_MODE)
endif()
# ==================================================================================================
# Definitions
# ==================================================================================================
@@ -612,13 +605,6 @@ 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
+3 -22
View File
@@ -119,8 +119,6 @@ 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)
@@ -179,18 +177,11 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/caching/VulkanDescriptorSetManager.h
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
src/vulkan/caching/VulkanPipelineLayoutCache.h
src/vulkan/memory/ResourceManager.cpp
src/vulkan/memory/ResourceManager.h
src/vulkan/memory/ResourcePointer.h
src/vulkan/memory/Resource.cpp
src/vulkan/memory/Resource.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
src/vulkan/spirv/VulkanSpirvUtils.cpp
src/vulkan/spirv/VulkanSpirvUtils.h
src/vulkan/VulkanAsyncHandles.cpp
src/vulkan/VulkanAsyncHandles.h
src/vulkan/VulkanBlitter.cpp
src/vulkan/VulkanBlitter.h
src/vulkan/VulkanBuffer.cpp
@@ -221,6 +212,9 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanSwapChain.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanResourceAllocator.h
src/vulkan/VulkanResources.cpp
src/vulkan/VulkanResources.h
src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h
src/vulkan/VulkanUtility.cpp
@@ -372,11 +366,6 @@ 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)
endif()
if (MSVC)
set(FILAMENT_WARNINGS /W3)
else()
@@ -388,9 +377,6 @@ else()
-Wover-aligned
-Werror
)
if (CMAKE_CXX_STANDARD EQUAL 20)
list(APPEND FILAMENT_WARNINGS -Wno-deprecated-this-capture)
endif()
endif()
if (APPLE)
@@ -400,7 +386,6 @@ endif()
target_compile_options(${TARGET} PRIVATE
${FILAMENT_WARNINGS}
${OSMESA_COMPILE_FLAGS}
$<$<CONFIG:Release>:${OPTIMIZATION_FLAGS}>
$<$<AND:$<PLATFORM_ID:Darwin>,$<CONFIG:Release>>:${DARWIN_OPTIMIZATION_FLAGS}>
)
@@ -410,7 +395,6 @@ if (FILAMENT_SUPPORTS_METAL)
endif()
target_link_libraries(${TARGET} PRIVATE
${OSMESA_LINKER_FLAGS}
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
)
@@ -445,7 +429,6 @@ 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
@@ -543,8 +526,6 @@ 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
+4 -26
View File
@@ -227,7 +227,6 @@ enum class DescriptorType : uint8_t {
SHADER_STORAGE_BUFFER,
SAMPLER,
INPUT_ATTACHMENT,
SAMPLER_EXTERNAL
};
enum class DescriptorFlags : uint8_t {
@@ -243,17 +242,8 @@ struct DescriptorSetLayoutBinding {
DescriptorType type;
ShaderStageFlags stageFlags;
descriptor_binding_t binding;
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;
}
DescriptorFlags flags;
uint16_t count;
};
struct DescriptorSetLayout {
@@ -319,20 +309,9 @@ 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.
*/
@@ -777,8 +756,7 @@ 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
ALL_ATTACHMENTS = COLOR_ATTACHMENT | DEPTH_ATTACHMENT | STENCIL_ATTACHMENT | SUBPASS_INPUT, //!< Mask of all attachments
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
};
inline const char* stringify(TextureUsage usage) {
+2 -13
View File
@@ -23,7 +23,6 @@
#include <utils/debug.h>
#include <type_traits> // FIXME: STL headers are not allowed in public headers
#include <utility>
#include <stdint.h>
@@ -107,18 +106,8 @@ struct Handle : public HandleBase {
Handle(Handle const& rhs) noexcept = default;
Handle(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;
}
Handle& operator=(Handle const& rhs) noexcept = default;
Handle& operator=(Handle&& rhs) noexcept = default;
explicit Handle(HandleId id) noexcept : HandleBase(id) { }
+7 -7
View File
@@ -68,6 +68,13 @@ 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;
/**
@@ -91,13 +98,6 @@ 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,6 +30,10 @@ 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) {
}
@@ -47,15 +51,14 @@ 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 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.
// For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
uint16_t layer = 0;
};
@@ -100,7 +103,7 @@ public:
// this is here for backward compatibility
MRT(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
: mInfos{{ handle, level, layer }} {
: mInfos{{ handle, level, layer, 0 }} {
}
};
@@ -89,11 +89,6 @@ protected:
*/
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
protected:
bool makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
private:
struct InitializeJvmForPerformanceManagerIfNeeded {
InitializeJvmForPerformanceManagerIfNeeded();
@@ -107,10 +102,6 @@ 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
@@ -1,64 +0,0 @@
/*
* 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;
void* mOsMesaApi = nullptr;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
@@ -88,7 +88,6 @@ public:
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {0, 0};
bool isProtected = false;
};
struct ImageSyncData {
@@ -200,13 +199,6 @@ 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()
@@ -275,23 +267,6 @@ 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,8 +139,7 @@ DECL_DRIVER_API_N(beginFrame,
DECL_DRIVER_API_N(setFrameScheduledCallback,
backend::SwapChainHandle, sch,
backend::CallbackHandler*, handler,
backend::FrameScheduledCallback&&, callback,
uint64_t, flags)
backend::FrameScheduledCallback&&, callback)
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 pool handle use-after-free
// check for 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,22 +179,6 @@ 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);
@@ -202,18 +186,14 @@ public:
template<typename B>
bool is_valid(Handle<B>& handle) {
if (!handle) {
// null handles are invalid
return false;
}
auto [p, tag] = handleToPointer(handle.getId());
if (isPoolHandle(handle.getId())) {
if (handle && isPoolHandle(handle.getId())) {
auto [p, tag] = handleToPointer(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 p != nullptr;
return true;
}
template<typename Dp, typename B>
-6
View File
@@ -41,10 +41,6 @@
#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)
@@ -128,8 +124,6 @@ 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
-28
View File
@@ -1,28 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H
#define TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H
#include <utils/Systrace.h>
#define PROFILE_SCOPE(marker) SYSTRACE_NAME(marker)
#define PROFILE_NAME_BEGINFRAME "backend::beginFrame"
#define PROFILE_NAME_ENDFRAME "backend::endFrame"
#endif // TNT_FILAMENT_BACKEND_SYSTRACEPROFILE_H
+6 -13
View File
@@ -73,11 +73,10 @@ public:
enum class Type {
NONE = 0,
GENERIC = 1,
RING = 2, // deprecated
RING = 2,
STAGING = 3,
DESCRIPTOR_SET = 4,
};
static constexpr size_t TypeCount = 4;
static constexpr size_t TypeCount = 3;
static constexpr auto toIndex(Type t) {
assert_invariant(t != Type::NONE);
@@ -89,8 +88,6 @@ public:
return 1;
case Type::STAGING:
return 2;
case Type::DESCRIPTOR_SET:
return 3;
}
}
@@ -174,10 +171,8 @@ 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.
*/
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);
void copyIntoBuffer(void* src, size_t size, size_t byteOffset);
void copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset);
/**
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
@@ -216,10 +211,8 @@ private:
BUMP_ALLOCATOR,
};
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;
void uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const;
void uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const;
UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer;
+10 -28
View File
@@ -66,20 +66,15 @@ MetalBuffer::~MetalBuffer() {
}
}
void MetalBuffer::copyIntoBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
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 << ", tag=" << getHandleTag();
<< " at offset " << byteOffset;
// The copy blit requires that byteOffset be a multiple of 4.
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) << "byteOffset must be a multiple of 4";
// If we have a cpu buffer, we can directly copy into it.
if (mCpuBuffer) {
@@ -89,18 +84,17 @@ void MetalBuffer::copyIntoBuffer(
switch (mUploadStrategy) {
case UploadStrategy::BUMP_ALLOCATOR:
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
uploadWithBumpAllocator(src, size, byteOffset);
break;
case UploadStrategy::POOL:
uploadWithPoolBuffer(src, size, byteOffset, std::move(getHandleTag));
uploadWithPoolBuffer(src, size, byteOffset);
break;
}
}
void MetalBuffer::copyIntoBufferUnsynchronized(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset) {
// TODO: implement the unsynchronized version
copyIntoBuffer(src, size, byteOffset, std::move(getHandleTag));
copyIntoBuffer(src, size, byteOffset);
}
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
@@ -206,13 +200,9 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
}
}
void MetalBuffer::uploadWithPoolBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) 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.
@@ -230,18 +220,10 @@ void MetalBuffer::uploadWithPoolBuffer(
}];
}
void MetalBuffer::uploadWithBumpAllocator(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const {
void MetalBuffer::uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const {
MetalBumpAllocator& allocator = *mContext.bumpAllocator;
auto [buffer, offset] = allocator.allocateStagingArea(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);
memcpy(static_cast<char*>(buffer.contents) + offset, src, size);
// Encode a blit from the staging buffer into the private GPU buffer.
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
+11 -1
View File
@@ -118,6 +118,9 @@ private:
};
struct MetalContext {
explicit MetalContext(size_t metalFreedTextureListSize)
: texturesToDestroy(metalFreedTextureListSize) {}
MetalDriver* driver;
id<MTLDevice> device = nullptr;
id<MTLCommandQueue> commandQueue = nullptr;
@@ -161,7 +164,6 @@ struct MetalContext {
CullModeStateTracker cullModeState;
WindingStateTracker windingState;
DepthClampStateTracker depthClampState;
ScissorRectStateTracker scissorRectState;
Handle<HwRenderPrimitive> currentRenderPrimitive;
// State caches.
@@ -185,6 +187,14 @@ 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;
+50 -105
View File
@@ -19,8 +19,6 @@
#include "CommandStreamDispatcher.h"
#include "metal/MetalDriver.h"
#include <filament/SwapChain.h>
#include "MetalBlitter.h"
#include "MetalBufferPool.h"
#include "MetalContext.h"
@@ -40,7 +38,6 @@
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/sstream.h>
#include <utils/Invocable.h>
#include <algorithm>
@@ -108,13 +105,13 @@ Dispatcher MetalDriver::getDispatcher() const noexcept {
return ConcreteDispatcher<MetalDriver>::make();
}
MetalDriver::MetalDriver(
MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
mContext(new MetalContext),
mHandleAllocator(
"Handles", driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck),
mStereoscopicType(driverConfig.stereoscopicType) {
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) {
mContext->driver = this;
TrackedMetalBuffer::setPlatform(platform);
@@ -254,19 +251,13 @@ 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, 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;
}
void MetalDriver::setFrameScheduledCallback(
Handle<HwSwapChain> sch, CallbackHandler* handler, FrameScheduledCallback&& callback) {
auto* swapChain = handle_cast<MetalSwapChain>(sch);
swapChain->setFrameScheduledCallback(handler, std::move(callback), flags);
swapChain->setFrameScheduledCallback(handler, std::move(callback));
}
void MetalDriver::setFrameCompletedCallback(
@@ -526,11 +517,6 @@ 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));
}
@@ -623,8 +609,6 @@ const char* toString(DescriptorType type) {
return "SAMPLER";
case DescriptorType::INPUT_ATTACHMENT:
return "INPUT_ATTACHMENT";
case DescriptorType::SAMPLER_EXTERNAL:
return "SAMPLER_EXTERNAL";
}
}
@@ -666,9 +650,10 @@ 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},\n",
DEBUG_LOG(" {binding = %d, type = %s, count = %d, stage = %s, flags = %s}",
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));
@@ -797,20 +782,9 @@ void MetalDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
}
void MetalDriver::destroyProgram(Handle<HwProgram> ph) {
if (UTILS_UNLIKELY(!ph)) {
return;
if (ph) {
destruct_handle<MetalProgram>(ph);
}
// Remove any cached pipeline states that refer to these programs.
auto* metalProgram = handle_cast<MetalProgram>(ph);
const auto& functions = metalProgram->getFunctionsIfPresent();
if (UTILS_LIKELY(functions.isRaster())) { // only raster pipelines are cached
const auto& raster = functions.getRasterFunctions();
mContext->pipelineStateCache.removeIf([&](const MetalPipelineState& state) {
const auto& [fragment, vertex] = raster;
return state.fragmentFunction == fragment || state.vertexFunction == vertex;
});
}
destruct_handle<MetalProgram>(ph);
}
void MetalDriver::destroyTexture(Handle<HwTexture> th) {
@@ -822,7 +796,15 @@ void MetalDriver::destroyTexture(Handle<HwTexture> th) {
auto* metalTexture = handle_cast<MetalTexture>(th);
mContext->textures.erase(metalTexture);
destruct_handle<MetalTexture>(th);
// 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());
}
}
void MetalDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
@@ -832,24 +814,17 @@ void MetalDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
}
void MetalDriver::destroySwapChain(Handle<HwSwapChain> sch) {
if (UTILS_UNLIKELY(!sch)) {
return;
}
auto* swapChain = handle_cast<MetalSwapChain>(sch);
if (mContext->currentDrawSwapChain == swapChain) {
mContext->currentDrawSwapChain = nullptr;
}
if (mContext->currentReadSwapChain == swapChain) {
mContext->currentReadSwapChain = nullptr;
}
// If the SwapChain is a pixel buffer, we need to wait for the current command buffer to
// complete before destroying it. This is because pixel buffer SwapChains hold a
// MetalExternalImage that could still being rendered into.
if (UTILS_UNLIKELY(swapChain->isPixelBuffer())) {
executeAfterCurrentCommandBufferCompletes(
[this, sch]() mutable { destruct_handle<MetalSwapChain>(sch); });
} else {
destruct_handle<MetalSwapChain>(sch);
if (sch) {
auto* swapChain = handle_cast<MetalSwapChain>(sch);
// If the SwapChain is a pixel buffer, we need to wait for the current command buffer to
// complete before destroying it. This is because pixel buffer SwapChains hold a
// MetalExternalImage that could still being rendered into.
if (UTILS_UNLIKELY(swapChain->isPixelBuffer())) {
executeAfterCurrentCommandBufferCompletes(
[this, sch]() mutable { destruct_handle<MetalSwapChain>(sch); });
} else {
destruct_handle<MetalSwapChain>(sch);
}
}
}
@@ -872,23 +847,19 @@ void MetalDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh)
void MetalDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
DEBUG_LOG("destroyDescriptorSet(dsh = %d)\n", dsh.getId());
if (!dsh) {
return;
if (dsh) {
executeAfterCurrentCommandBufferCompletes(
[this, dsh]() mutable { destruct_handle<MetalDescriptorSet>(dsh); });
}
// Unbind this descriptor set.
auto* descriptorSet = handle_cast<MetalDescriptorSet>(dsh);
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
if (UTILS_UNLIKELY(mContext->currentDescriptorSets[i] == descriptorSet)) {
mContext->currentDescriptorSets[i] = nullptr;
}
}
executeAfterCurrentCommandBufferCompletes(
[this, dsh]() mutable { destruct_handle<MetalDescriptorSet>(dsh); });
}
void MetalDriver::terminate() {
// 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();
@@ -1112,34 +1083,24 @@ 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,
[&]() { return mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe(); });
ib->buffer.copyIntoBuffer(data.buffer, data.size, byteOffset);
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. 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();
<< "updateBufferObject must be called outside of a render pass.";
auto* bo = handle_cast<MetalBufferObject>(boh);
bo->updateBuffer(data.buffer, data.size, byteOffset,
[&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); });
bo->updateBuffer(data.buffer, data.size, byteOffset);
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,
[&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); });
bo->updateBufferUnsynchronized(data.buffer, data.size, byteOffset);
scheduleDestroy(std::move(data));
}
@@ -1257,7 +1218,6 @@ 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();
@@ -1636,7 +1596,6 @@ 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);
@@ -1824,21 +1783,11 @@ void MetalDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
if (UTILS_UNLIKELY(!dsh)) {
DEBUG_LOG("bindDescriptorSet(dsh = null, set = %d, offsets = [])\n", set);
mContext->currentDescriptorSets[set] = nullptr;
mContext->vertexDescriptorBindings.setBuffer(nil, 0, set);
mContext->fragmentDescriptorBindings.setBuffer(nil, 0, set);
mContext->dynamicOffsets.setOffsets(set, nullptr, 0);
return;
}
auto descriptorSet = handle_cast<MetalDescriptorSet>(dsh);
const size_t dynamicBindings = descriptorSet->layout->getDynamicOffsetCount();
utils::FixedCapacityVector<size_t> offsetsVector(dynamicBindings, 0);
#if FILAMENT_METAL_DEBUG_LOG == 1
printf("[METAL DEBUG] bindDescriptorSet(dsh = %d, set = %d, offsets = [", dsh.getId(), set);
DEBUG_LOG("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) {
@@ -1847,7 +1796,7 @@ void MetalDriver::bindDescriptorSet(
offsetsVector[i] = offsets[i];
}
printf("])\n");
DEBUG_LOG("])\n");
#endif
// Bind the descriptor set.
@@ -1994,11 +1943,7 @@ void MetalDriver::scissor(Viewport scissorBox) {
.height = static_cast<NSUInteger>(bottom - top)
};
auto& srs = mContext->scissorRectState;
srs.updateState(scissorRect);
if (srs.stateChanged()) {
[mContext->currentRenderPassEncoder setScissorRect:scissorRect];
}
[mContext->currentRenderPassEncoder setScissorRect:scissorRect];
}
void MetalDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
+1 -2
View File
@@ -71,8 +71,7 @@ constexpr inline MTLIndexType getIndexType(size_t elementSize) noexcept {
} else if (elementSize == 4) {
return MTLIndexTypeUInt32;
}
assert_invariant(false);
return MTLIndexTypeUInt16;
FILAMENT_CHECK_POSTCONDITION(false) << "Index element size not supported.";
}
constexpr inline MTLVertexFormat getMetalFormat(ElementType type, bool normalized) noexcept {
+27 -12
View File
@@ -74,8 +74,7 @@ public:
void releaseDrawable();
void setFrameScheduledCallback(
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
void setFrameCompletedCallback(
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
@@ -124,7 +123,6 @@ private:
struct {
CallbackHandler* handler = nullptr;
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
uint64_t flags = 0;
} frameScheduled;
struct {
@@ -135,15 +133,11 @@ 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, TagResolver&& getHandleTag);
void updateBufferUnsynchronized(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag);
void updateBuffer(void* data, size_t size, uint32_t byteOffset);
void updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset);
MetalBuffer* getBuffer() { return &buffer; }
private:
@@ -202,7 +196,6 @@ public:
MetalProgram(MetalContext& context, Program&& program) noexcept;
const MetalShaderCompiler::MetalFunctionBundle& getFunctions();
const MetalShaderCompiler::MetalFunctionBundle& getFunctionsIfPresent() const;
private:
void initialize();
@@ -272,6 +265,26 @@ 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;
@@ -288,6 +301,8 @@ 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 {
@@ -438,7 +453,7 @@ struct MetalTimerQuery : public HwTimerQuery {
struct Status {
std::atomic<bool> available {false};
std::atomic<uint64_t> elapsed {0}; // only valid if available is true
uint64_t elapsed {0}; // only valid if available is true
};
std::shared_ptr<Status> status;
@@ -496,7 +511,7 @@ struct MetalDescriptorSet : public HwDescriptorSet {
std::vector<std::shared_ptr<MetalExternalImage>> externalImages;
std::array<TrackedMetalBuffer, Program::SHADER_TYPE_COUNT> cachedBuffer = { nil };
std::array<id<MTLBuffer>, Program::SHADER_TYPE_COUNT> cachedBuffer = { nil };
};
} // namespace backend
+32 -50
View File
@@ -229,10 +229,9 @@ void MetalSwapChain::ensureDepthStencilTexture() {
}
void MetalSwapChain::setFrameScheduledCallback(
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
CallbackHandler* handler, FrameScheduledCallback&& callback) {
frameScheduled.handler = handler;
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
frameScheduled.flags = flags;
}
void MetalSwapChain::setFrameCompletedCallback(
@@ -261,10 +260,10 @@ public:
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags) {
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver) {
assert_invariant(drawableMutex);
assert_invariant(driver);
return new PresentDrawableData(drawable, drawableMutex, driver, flags);
return new PresentDrawableData(drawable, drawableMutex, driver);
}
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
@@ -272,23 +271,16 @@ public:
[that->mDrawable present];
}
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);
});
}
// 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, uint64_t flags)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {}
MetalDriver* driver)
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver) {}
static void cleanupAndDestroy(PresentDrawableData *that) {
if (that->mDrawable) {
@@ -303,7 +295,6 @@ private:
id<CAMetalDrawable> mDrawable;
std::shared_ptr<std::mutex> mDrawableMutex;
MetalDriver* mDriver = nullptr;
uint64_t mFlags = 0;
};
void presentDrawable(bool presentFrame, void* user) {
@@ -320,8 +311,8 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
struct Callback {
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver, uint64_t flags)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {}
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver)
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver)) {}
std::shared_ptr<FrameScheduledCallback> f;
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
std::unique_ptr<PresentDrawableData> data;
@@ -336,19 +327,14 @@ 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, flags);
frameScheduled.callback, drawable, layerDrawableMutex, context.driver);
backend::CallbackHandler* handler = frameScheduled.handler;
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
Callback* user = callback.release();
if (flags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
Callback::func(user);
} else {
driver->scheduleCallback(handler, user, &Callback::func);
}
driver->scheduleCallback(handler, user, &Callback::func);
}];
}
@@ -383,14 +369,12 @@ 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, TagResolver&& getHandleTag) {
buffer.copyIntoBuffer(data, size, byteOffset, std::move(getHandleTag));
void MetalBufferObject::updateBuffer(void* data, size_t size, uint32_t byteOffset) {
buffer.copyIntoBuffer(data, size, byteOffset);
}
void MetalBufferObject::updateBufferUnsynchronized(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset, std::move(getHandleTag));
void MetalBufferObject::updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset) {
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset);
}
MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount,
@@ -495,10 +479,6 @@ const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctions() {
return mFunctionBundle;
}
const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctionsIfPresent() const {
return mFunctionBundle;
}
void MetalProgram::initialize() {
if (!mToken) {
return;
@@ -654,6 +634,14 @@ 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;
}
@@ -1294,8 +1282,7 @@ id<MTLArgumentEncoder> MetalDescriptorSetLayout::getArgumentEncoderSlow(id<MTLDe
[arguments addObject:bufferArgument];
break;
}
case DescriptorType::SAMPLER:
case DescriptorType::SAMPLER_EXTERNAL: {
case DescriptorType::SAMPLER: {
MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor];
textureArgument.index = binding.binding * 2;
textureArgument.dataType = MTLDataTypeTexture;
@@ -1358,7 +1345,7 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
auto& buffer = cachedBuffer[index];
if (buffer) {
return buffer.get();
return buffer;
}
// Map all the texture bindings to their respective texture types.
@@ -1381,13 +1368,9 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
id<MTLArgumentEncoder> encoder =
layout->getArgumentEncoder(context.device, stage, textureTypes);
{
ScopedAllocationTimer timer("descriptor_set");
buffer = { [context.device newBufferWithLength:encoder.encodedLength
options:MTLResourceStorageModeShared],
TrackedMetalBuffer::Type::DESCRIPTOR_SET };
}
[encoder setArgumentBuffer:buffer.get() offset:0];
buffer = [context.device newBufferWithLength:encoder.encodedLength
options:MTLResourceStorageModeShared];
[encoder setArgumentBuffer:buffer offset:0];
for (auto const& binding : bindings) {
if (!hasShaderType(binding.stageFlags, stage)) {
@@ -1410,8 +1393,7 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
atIndex:binding.binding * 2];
break;
}
case DescriptorType::SAMPLER:
case DescriptorType::SAMPLER_EXTERNAL: {
case DescriptorType::SAMPLER: {
auto found = textures.find(binding.binding);
if (found == textures.end()) {
[encoder setTexture:driver->mContext->emptyTexture atIndex:binding.binding * 2];
@@ -1435,7 +1417,7 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
}
}
return buffer.get();
return buffer;
}
} // namespace backend
@@ -83,10 +83,6 @@ public:
return std::get<Compute>(mPrograms);
}
bool isRaster() const { return std::holds_alternative<Raster>(mPrograms); }
bool isCompute() const { return std::holds_alternative<Compute>(mPrograms); }
static MetalFunctionBundle none() {
return MetalFunctionBundle(None{});
}
+6 -30
View File
@@ -28,9 +28,7 @@
#include <memory>
#include <tsl/robin_map.h>
#include <utils/Hash.h>
#include <utils/Invocable.h>
namespace filament {
namespace backend {
@@ -162,8 +160,6 @@ template<typename StateType,
typename HashFn = utils::hash::MurmurHashFn<StateType>>
class StateCache {
using MapType = tsl::robin_map<StateType, MetalType, HashFn>;
public:
StateCache() = default;
@@ -173,18 +169,6 @@ public:
void setDevice(id<MTLDevice> device) noexcept { mDevice = device; }
void removeIf(utils::Invocable<bool(const StateType&)> fn) noexcept {
typename MapType::const_iterator it = mStateCache.begin();
while (it != mStateCache.end()) {
const auto& [key, _] = *it;
if (UTILS_UNLIKELY(fn(key))) {
it = mStateCache.erase(it);
} else {
++it;
}
}
}
MetalType getOrCreateState(const StateType& state) noexcept {
assert_invariant(mDevice);
@@ -212,7 +196,7 @@ private:
StateCreator creator;
id<MTLDevice> mDevice = nil;
MapType mStateCache;
tsl::robin_map<StateType, MetalType, HashFn> mStateCache;
};
@@ -220,8 +204,9 @@ 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, typename StateEqual = std::equal_to<StateType>>
template<typename StateType>
class StateTracker {
public:
// Call to force the state to dirty at the beginning of each frame, as all state must be
@@ -229,7 +214,7 @@ public:
void invalidate() noexcept { mStateDirty = true; }
void updateState(const StateType& newState) noexcept {
if (!StateEqual()(mCurrentState, newState)) {
if (mCurrentState != newState) {
mCurrentState = newState;
mStateDirty = true;
}
@@ -250,6 +235,7 @@ private:
bool mStateDirty = true;
StateType mCurrentState = {};
};
// Pipeline state
@@ -357,16 +343,6 @@ 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;
@@ -458,7 +434,7 @@ public:
private:
static_assert(N <= 8);
std::array<__weak id<MTLBuffer>, N> mBuffers = { nil };
std::array<__unsafe_unretained id<MTLBuffer>, N> mBuffers = { nil };
std::array<NSUInteger, N> mOffsets = { 0 };
utils::bitset8 mDirtyBuffers;
utils::bitset8 mDirtyOffsets;
+1 -1
View File
@@ -54,7 +54,7 @@ void NoopDriver::beginFrame(int64_t monotonic_clock_ns,
}
void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
CallbackHandler* handler, FrameScheduledCallback&& callback) {
}
+1 -3
View File
@@ -69,9 +69,7 @@ public:
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
assert_invariant(binding < MAX_DESCRIPTOR_COUNT);
assert_invariant(entry.binding < 128); // we reserve 1 bit for the type right now
mStorage[set][binding] = { (uint8_t)entry.binding,
entry.type == DescriptorType::SAMPLER ||
entry.type == DescriptorType::SAMPLER_EXTERNAL };
mStorage[set][binding] = { (uint8_t)entry.binding, entry.type == DescriptorType::SAMPLER };
mActiveDescriptors[set].set(binding);
}
@@ -67,9 +67,7 @@ GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle ds
bool const dynamicOffset = any(entry.flags & DescriptorFlags::DYNAMIC_OFFSET);
dynamicBuffers.set(index, dynamicOffset);
if (UTILS_UNLIKELY(gl.isES2())) {
if (dynamicOffset) {
dynamicBufferCount++;
}
dynamicBufferCount++;
desc.emplace<BufferGLES2>(dynamicOffset);
} else {
auto const type = GLUtils::getBufferBindingType(BufferObjectBinding::UNIFORM);
@@ -96,7 +94,6 @@ GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle ds
break;
}
case DescriptorType::SAMPLER:
case DescriptorType::SAMPLER_EXTERNAL:
if (UTILS_UNLIKELY(gl.isES2())) {
desc.emplace<SamplerGLES2>();
} else {
@@ -78,12 +78,8 @@ struct GLDescriptorSet : public HwDescriptorSet {
private:
// a Buffer Descriptor such as SSBO or UBO with static offset
struct Buffer {
// 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) {}
Buffer() = default;
explicit Buffer(GLenum target) noexcept : target(target) { }
GLenum target; // 4
GLuint id = 0; // 4
uint32_t offset = 0; // 4
+28 -35
View File
@@ -552,14 +552,6 @@ 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;
}
@@ -614,7 +606,7 @@ FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
gets.max_combined_texture_image_units >=
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
}
}
@@ -623,13 +615,15 @@ FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
# ifndef IOS // IOS is guaranteed to have ES3.x
else if (UTILS_UNLIKELY(major == 2)) {
// Runtime OpenGL version is ES 2.x
// note: mandatory extensions (all supported by Mali-400 and Adreno 304)
// OES_depth_texture
// OES_depth24
// OES_packed_depth_stencil
// OES_rgb8_rgba8
// OES_standard_derivatives
// OES_texture_npot
# if defined(BACKEND_OPENGL_LEVEL_GLES30)
// mandatory extensions (all supported by Mali-400 and Adreno 304)
assert_invariant(exts.OES_depth_texture);
assert_invariant(exts.OES_depth24);
assert_invariant(exts.OES_packed_depth_stencil);
assert_invariant(exts.OES_rgb8_rgba8);
assert_invariant(exts.OES_standard_derivatives);
assert_invariant(exts.OES_texture_npot);
# endif
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
}
# endif // IOS
@@ -935,19 +929,15 @@ void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
}
}
void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept {
glDeleteBuffers(1, &buffer);
void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept {
glDeleteBuffers(n, buffers);
// bindings of bound buffers are reset to 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);
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;
}
}
@@ -956,13 +946,16 @@ void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept {
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
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;
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;
}
}
}
}
+1 -9
View File
@@ -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 deleteBuffer(GLuint buffer, GLenum target) noexcept;
void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept;
void deleteVertexArray(GLuint vao) noexcept;
void destroyWithContext(size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept;
@@ -316,15 +316,10 @@ 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.
@@ -559,9 +554,6 @@ private:
{ bugs.delay_fbo_destruction,
"delay_fbo_destruction",
""},
{ bugs.rebind_buffer_after_deletion,
"rebind_buffer_after_deletion",
""},
{ bugs.force_feature_level0,
"force_feature_level0",
""},
+36 -71
View File
@@ -23,7 +23,6 @@
#include "OpenGLDriverFactory.h"
#include "OpenGLProgram.h"
#include "OpenGLTimerQuery.h"
#include "SystraceProfile.h"
#include "gl_headers.h"
#include <backend/platforms/OpenGLPlatform.h>
@@ -92,47 +91,24 @@
#define DEBUG_GROUP_MARKER_NONE 0x00 // no debug marker
#define DEBUG_GROUP_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_GROUP_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_GROUP_MARKER_ALL 0xFF // all markers
#define DEBUG_GROUP_MARKER_ALL 0x03 // all markers
#define DEBUG_MARKER_NONE 0x00 // no debug marker
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_MARKER_PROFILE 0x04 // profiling on the backend side (systrace)
#define DEBUG_MARKER_ALL (0xFF & ~DEBUG_MARKER_PROFILE) // all markers
#define DEBUG_MARKER_ALL 0x03 // all markers
// set to the desired debug marker level (for user markers [default: All])
#define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_ALL
// set to the desired debug level (for internal debugging [Default: None])
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
// Override the debug markers if we are forcing profiling mode
#if defined(FILAMENT_FORCE_PROFILING_MODE)
# undef DEBUG_GROUP_MARKER_LEVEL
# undef DEBUG_MARKER_LEVEL
# define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_NONE
# define DEBUG_MARKER_LEVEL DEBUG_MARKER_PROFILE
#endif
#if DEBUG_MARKER_LEVEL == DEBUG_MARKER_PROFILE
# define DEBUG_MARKER()
# define PROFILE_MARKER(marker) PROFILE_SCOPE(marker);
# if DEBUG_GROUP_MARKER_LEVEL != DEBUG_GROUP_MARKER_NONE
# error PROFILING is exclusive; group markers must be disabled.
# endif
# ifndef NDEBUG
# error PROFILING is meaningless in DEBUG mode.
# endif
#elif DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
# define DEBUG_MARKER() DebugMarker _debug_marker(*this, __func__);
# define PROFILE_MARKER(marker) DEBUG_MARKER()
# if DEBUG_MARKER_LEVEL & DEBUG_MARKER_PROFILE
# error PROFILING is exclusive; all other debug features must be disabled.
# endif
#if DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
# define DEBUG_MARKER() \
DebugMarker _debug_marker(*this, __func__);
#else
# define DEBUG_MARKER()
# define PROFILE_MARKER(marker)
#endif
using namespace filament::math;
@@ -406,21 +382,24 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
}
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
bool success = true;
if (mBoundProgram != p) {
// compile/link the program if needed and call glUseProgram
success = p->use(this, mContext);
assert_invariant(success == p->isValid());
if (success) {
// TODO: we could even improve this if the program could tell us which of the descriptors
// bindings actually changed. In practice, it is likely that set 0 or 1 might not
// change often.
decltype(mInvalidDescriptorSetBindings) changed;
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
mInvalidDescriptorSetBindings |= changed;
if (UTILS_UNLIKELY(mBoundProgram == p)) {
// program didn't change, don't do anything.
return true;
}
mBoundProgram = p;
}
// compile/link the program if needed and call glUseProgram
bool const success = p->use(this, mContext);
assert_invariant(success == p->isValid());
if (success) {
// TODO: we could even improve this if the program could tell us which of the descriptors
// bindings actually changed. In practice, it is likely that set 0 or 1 might not
// change often.
decltype(mInvalidDescriptorSetBindings) changed;
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
mInvalidDescriptorSetBindings |= changed;
mBoundProgram = p;
}
if (UTILS_UNLIKELY(mContext.isES2() && success)) {
@@ -1287,7 +1266,6 @@ 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
@@ -1298,9 +1276,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
target = getCubemapTarget(binfo.layer);
// note: cubemaps can't be multi-sampled
break;
case SamplerType::SAMPLER_EXTERNAL:
// This is an error. We have asserted in debug build.
target = t->gl.target;
default:
break;
}
}
@@ -1318,7 +1294,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);
@@ -1342,7 +1318,6 @@ 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
@@ -1352,7 +1327,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.layer, layerCount);
t->gl.id, 0, binfo.baseViewIndex, layerCount);
} else
#endif // !defined(__EMSCRIPTEN__) && !defined(IOS)
{
@@ -1456,7 +1431,6 @@ 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
@@ -1682,7 +1656,7 @@ void OpenGLDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
// See if we need the emulated rec709 output conversion
if (UTILS_UNLIKELY(mContext.isES2())) {
sc->rec709 = ((flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) &&
sc->rec709 = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE &&
!mPlatform.isSRGBSwapChainSupported());
}
}
@@ -1753,7 +1727,7 @@ void OpenGLDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (ibh) {
auto& gl = mContext;
GLIndexBuffer const* ib = handle_cast<const GLIndexBuffer*>(ibh);
gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
gl.deleteBuffers(1, &ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
destruct(ibh, ib);
}
}
@@ -1766,7 +1740,7 @@ void OpenGLDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
free(bo->gl.buffer);
} else {
gl.deleteBuffer(bo->gl.id, bo->gl.binding);
gl.deleteBuffers(1, &bo->gl.id, bo->gl.binding);
}
destruct(boh, bo);
}
@@ -2935,9 +2909,6 @@ 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
@@ -2950,6 +2921,7 @@ 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);
}
@@ -2965,6 +2937,7 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
}
if (any(clearFlags)) {
gl.disable(GL_SCISSOR_TEST);
clearWithRasterPipe(clearFlags,
params.clearColor, (GLfloat)params.clearDepth, (GLint)params.clearStencil);
}
@@ -2983,6 +2956,7 @@ 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
@@ -3469,7 +3443,7 @@ void OpenGLDriver::beginFrame(
UTILS_UNUSED int64_t monotonic_clock_ns,
UTILS_UNUSED int64_t refreshIntervalNs,
UTILS_UNUSED uint32_t frameId) {
PROFILE_MARKER(PROFILE_NAME_BEGINFRAME)
DEBUG_MARKER()
auto& gl = mContext;
insertEventMarker("beginFrame");
mPlatform.beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
@@ -3488,8 +3462,8 @@ void OpenGLDriver::beginFrame(
}
}
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
FrameScheduledCallback&& callback, uint64_t flags) {
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, FrameScheduledCallback&& callback) {
DEBUG_MARKER()
}
@@ -3504,7 +3478,7 @@ void OpenGLDriver::setPresentationTime(int64_t monotonic_clock_ns) {
}
void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
PROFILE_MARKER(PROFILE_NAME_ENDFRAME)
DEBUG_MARKER()
#if defined(__EMSCRIPTEN__)
// WebGL builds are single-threaded so users might manipulate various GL state after we're
// done with the frame. We do NOT officially support using Filament in this way, but we can
@@ -3896,14 +3870,6 @@ void OpenGLDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
if (UTILS_UNLIKELY(!dsh)) {
mBoundDescriptorSets[set].dsh = dsh;
mInvalidDescriptorSetBindings.set(set, true);
mInvalidDescriptorSetBindingOffsets.set(set, true);
return;
}
// handle_cast<> here also serves to validate the handle (it actually cannot return nullptr)
GLDescriptorSet const* const ds = handle_cast<GLDescriptorSet*>(dsh);
if (ds) {
@@ -3921,7 +3887,6 @@ 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());
}
@@ -174,8 +174,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
break;
}
case DescriptorType::SAMPLER:
case DescriptorType::SAMPLER_EXTERNAL: {
case DescriptorType::SAMPLER: {
if (!entry.name.empty()) {
GLint const loc = glGetUniformLocation(program, entry.name.c_str());
if (loc >= 0) {
@@ -255,9 +254,7 @@ void OpenGLProgram::updateUniforms(
for (size_t i = 0, c = records.uniforms.size(); i < c; i++) {
Program::Uniform const& u = records.uniforms[i];
GLint const loc = records.locations[i];
// mRec709Location is special, it is handled by setRec709ColorSpace() and the corresponding
// entry in `buffer` is typically not initialized, so we skip it.
if (loc < 0 || loc == mRec709Location) {
if (loc < 0) {
continue;
}
// u.offset is in 'uint32_t' units
@@ -155,6 +155,10 @@ 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");
@@ -553,8 +557,6 @@ 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,14 +25,12 @@
#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>
@@ -44,9 +42,7 @@
#include <chrono>
#include <new>
#include <string_view>
#include <dlfcn.h>
#include <unistd.h>
#include <stddef.h>
@@ -84,6 +80,8 @@ UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID =
}
using namespace glext;
using EGLStream = Platform::Stream;
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
@@ -103,73 +101,38 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept
mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mInitializeJvmForPerformanceManagerIfNeeded(),
mPerformanceHintManager() {
mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
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;
}
mNativeWindowLib = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (mNativeWindowLib) {
ANativeWindow_getBuffersDefaultDataSpace =
(int32_t(*)(ANativeWindow*))dlsym(mNativeWindowLib,
"ANativeWindow_getBuffersDefaultDataSpace");
}
// 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);
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
if (mNativeWindowLib) {
dlclose(mNativeWindowLib);
}
}
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default;
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,
@@ -226,8 +189,6 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
"eglGetFrameTimestampsANDROID");
}
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
return driver;
}
@@ -1,149 +0,0 @@
/*
* 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 <dlfcn.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;
};
struct OSMesaAPI {
private:
using CreateContextFunc = OSMesaContext (*)(GLenum format, OSMesaContext);
using DestroyContextFunc = GLboolean (*)(OSMesaContext);
using MakeCurrentFunc = GLboolean (*)(OSMesaContext ctx, void* buffer, GLenum type,
GLsizei width, GLsizei height);
using GetProcAddressFunc = OSMESAproc (*)(const char* funcName);
public:
CreateContextFunc OSMesaCreateContext;
DestroyContextFunc OSMesaDestroyContext;
MakeCurrentFunc OSMesaMakeCurrent;
GetProcAddressFunc OSMesaGetProcAddress;
OSMesaAPI() {
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
for (char const* libName: libraryNames) {
mLib = dlopen(libName, RTLD_GLOBAL | RTLD_NOW);
if (mLib) {
break;
}
}
FILAMENT_CHECK_PRECONDITION(mLib)
<< "Unable to dlopen libOSMesa to create a software GL context";
OSMesaGetProcAddress = (GetProcAddressFunc) dlsym(mLib, "OSMesaGetProcAddress");
OSMesaCreateContext = (CreateContextFunc) OSMesaGetProcAddress("OSMesaCreateContext");
OSMesaDestroyContext =
(DestroyContextFunc) OSMesaGetProcAddress("OSMesaDestroyContext");
OSMesaMakeCurrent = (MakeCurrentFunc) OSMesaGetProcAddress("OSMesaMakeCurrent");
}
~OSMesaAPI() {
dlclose(mLib);
}
private:
void* mLib = nullptr;
};
}// anonymous namespace
Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
const DriverConfig& driverConfig) noexcept {
OSMesaAPI* api = new OSMesaAPI();
mOsMesaApi = api;
FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr)
<< "shared GL context is not supported with PlatformOSMesa";
mContext = api->OSMesaCreateContext(GL_RGBA, NULL);
// We need to do a no-op makecurrent here so that the context will be in a correct state before
// any GL calls.
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 {
OSMesaAPI* api = (OSMesaAPI*) mOsMesaApi;
api->OSMesaDestroyContext(mContext);
delete api;
mOsMesaApi = nullptr;
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 {
OSMesaAPI* api = (OSMesaAPI*) mOsMesaApi;
OSMesaSwapchain* impl = (OSMesaSwapchain*) drawSwapChain;
auto result = api->OSMesaMakeCurrent(mContext, (BackingType*) impl->buffer.get(),
BACKING_GL_TYPE, impl->width, impl->height);
FILAMENT_CHECK_POSTCONDITION(result == GL_TRUE) << "OSMesaMakeCurrent failed!";
return true;
}
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,8 +37,9 @@
namespace {
void reportWindowsError(DWORD dwError) {
void reportLastWindowsError() {
LPSTR lpMessageBuffer = nullptr;
DWORD dwError = GetLastError();
if (dwError == 0) {
return;
@@ -79,7 +80,6 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
const Platform::DriverConfig& driverConfig) noexcept {
int result = 0;
int pixelFormat = 0;
DWORD dwError = 0;
mPfd = {
sizeof(PIXELFORMATDESCRIPTOR),
@@ -105,7 +105,6 @@ 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;
}
@@ -116,7 +115,6 @@ 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;
@@ -138,7 +136,6 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
if (mContext) {
break;
}
dwError = GetLastError();
}
if (!mContext) {
@@ -151,7 +148,6 @@ 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;
@@ -166,7 +162,7 @@ error:
if (tempContext) {
wglDeleteContext(tempContext);
}
reportWindowsError(dwError);
reportLastWindowsError();
terminate();
return NULL;
}
@@ -209,11 +205,9 @@ 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 (!swapChain->hDc) {
DWORD dwError = GetLastError();
ASSERT_POSTCONDITION_NON_FATAL(swapChain->hDc,
"Unable to create the SwapChain (nativeWindow = %p)", nativeWindow);
reportWindowsError(dwError);
if (!ASSERT_POSTCONDITION_NON_FATAL(swapChain->hDc,
"Unable to create the SwapChain (nativeWindow = %p)", nativeWindow)) {
reportLastWindowsError();
}
// We have to match pixel formats across the HDC and HGLRC (mContext)
@@ -270,10 +264,8 @@ bool PlatformWGL::makeCurrent(ContextType type, SwapChain* drawSwapChain,
HDC hdc = wglSwapChain->hDc;
if (hdc != NULL) {
BOOL success = wglMakeCurrent(hdc, mContext);
if (!success) {
DWORD dwError = GetLastError();
ASSERT_POSTCONDITION_NON_FATAL(success, "wglMakeCurrent() failed. hdc = %p", hdc);
reportWindowsError(dwError);
if (!ASSERT_POSTCONDITION_NON_FATAL(success, "wglMakeCurrent() failed. hdc = %p", hdc)) {
reportLastWindowsError();
wglMakeCurrent(0, NULL);
}
}
+1
View File
@@ -410,6 +410,7 @@ 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 << "}";
}
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "VulkanAsyncHandles.h"
namespace filament::backend {
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
: mStartingQueryIndex(std::get<0>(indices)),
mStoppingQueryIndex(std::get<1>(indices)) {}
void VulkanTimerQuery::setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
std::unique_lock<utils::Mutex> lock(mFenceMutex);
mFence = fence;
}
bool VulkanTimerQuery::isCompleted() noexcept {
std::unique_lock<utils::Mutex> lock(mFenceMutex);
// QueryValue is a synchronous call and might occur before beginTimerQuery has written anything
// into the command buffer, which is an error according to the validation layer that ships in
// the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to require that the
// timestamp has at least been written into a processed command buffer.
// This fence indicates that the corresponding buffer has been completed.
return mFence && mFence->getStatus() == VK_SUCCESS;
}
VulkanTimerQuery::~VulkanTimerQuery() = default;
} // namespace filament::backend
@@ -1,81 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANASYNCHANDLES_H
#define TNT_FILAMENT_BACKEND_VULKANASYNCHANDLES_H
#include <bluevk/BlueVK.h>
#include "DriverBase.h"
#include "vulkan/memory/Resource.h"
#include <utils/Mutex.h>
#include <utils/Condition.h>
namespace filament::backend {
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
struct VulkanCmdFence {
VulkanCmdFence(VkResult initialStatus) {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
status.store(initialStatus);
}
~VulkanCmdFence() = default;
void setStatus(VkResult value) { status.store(value); }
VkResult getStatus() { return status.load(std::memory_order_acquire); }
private:
std::atomic<VkResult> status;
};
struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
VulkanFence() {}
std::shared_ptr<VulkanCmdFence> fence;
};
struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource {
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
~VulkanTimerQuery();
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept;
bool isCompleted() noexcept;
uint32_t getStartingQueryIndex() const {
return mStartingQueryIndex;
}
uint32_t getStoppingQueryIndex() const {
return mStoppingQueryIndex;
}
private:
uint32_t mStartingQueryIndex;
uint32_t mStoppingQueryIndex;
std::shared_ptr<VulkanCmdFence> mFence;
utils::Mutex mFenceMutex;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANHASYNCANDLES_H
@@ -66,10 +66,10 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V
1, blitRegions, filter);
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
oldSrcLayout = src.texture->getDefaultLayout();
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
}
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = dst.texture->getDefaultLayout();
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
}
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 = src.texture->getDefaultLayout();
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
}
if (oldDstLayout == VulkanLayout::UNDEFINED) {
oldDstLayout = dst.texture->getDefaultLayout();
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
}
src.texture->transitionLayout(commands, srcRange, oldSrcLayout);
dst.texture->transitionLayout(commands, dstRange, oldDstLayout);
@@ -126,7 +126,8 @@ struct BlitterUniforms {
}// anonymous namespace
VulkanBlitter::VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept
: mPhysicalDevice(physicalDevice), mCommands(commands) {}
: mPhysicalDevice(physicalDevice),
mCommands(commands) {}
void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
@@ -150,8 +151,7 @@ void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
}
#endif
VulkanCommandBuffer& commands = dst.texture->getIsProtected() ?
mCommands->getProtected() : mCommands->get();
VulkanCommandBuffer& commands = mCommands->get();
commands.acquire(src.texture);
commands.acquire(dst.texture);
resolveFast(&commands, aspect, src, dst);
@@ -176,8 +176,7 @@ void VulkanBlitter::blit(VkFilter filter,
#endif
// src and dst should have the same aspect here
VkImageAspectFlags const aspect = src.texture->getImageAspect();
VulkanCommandBuffer& commands = dst.texture->getIsProtected() ?
mCommands->getProtected() : mCommands->get();
VulkanCommandBuffer& commands = mCommands->get();
commands.acquire(src.texture);
commands.acquire(dst.texture);
blitFast(&commands, aspect, filter, src, dst, srcRectPair, dstRectPair);
+354 -395
View File
@@ -33,194 +33,275 @@ using namespace utils;
namespace filament::backend {
namespace {
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
using Timestamp = VulkanGroupMarkers::Timestamp;
#endif
VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) {
VkCommandBuffer cmdbuffer;
// Create the low-level command buffer.
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, &cmdbuffer);
return cmdbuffer;
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);
}
} // anonymous namespace
VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device,
VkCommandPool pool)
: mResourceManager(allocator),
mPipeline(VK_NULL_HANDLE) {
// 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,
};
// 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);
}
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;
}
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
mMarkers.push_back({marker,
start.time_since_epoch().count() > 0.0
? start
: std::chrono::high_resolution_clock::now()});
mMarkers.push_back(marker);
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
mTimestamps.push_back(start.time_since_epoch().count() > 0.0
? start
: std::chrono::high_resolution_clock::now());
#endif
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
auto ret = mMarkers.back();
auto const marker = mMarkers.back();
mMarkers.pop_back();
return ret;
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
auto const timestamp = mTimestamps.back();
mTimestamps.pop_back();
return std::make_pair(marker, timestamp);
#else
return std::make_pair(marker, Timestamp{});
#endif
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
auto ret = mMarkers.front();
auto const marker = mMarkers.front();
mMarkers.pop_front();
return ret;
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
auto const timestamp = mTimestamps.front();
mTimestamps.pop_front();
return std::make_pair(marker, timestamp);
#else
return std::make_pair(marker, Timestamp{});
#endif
}
std::pair<std::string, Timestamp> const& VulkanGroupMarkers::top() const {
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
assert_invariant(!empty());
return mMarkers.back();
auto const marker = mMarkers.back();
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
auto const topTimestamp = mTimestamps.front();
return std::make_pair(marker, topTimestamp);
#else
return std::make_pair(marker, Timestamp{});
#endif
}
bool VulkanGroupMarkers::empty() const noexcept {
return mMarkers.empty();
}
#endif // FVK_DEBUG_GROUP_MARKERS
VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext* context, VkDevice device, VkQueue queue,
VkCommandPool pool, bool isProtected)
: mContext(context),
mMarkerCount(0),
isProtected(isProtected),
mDevice(device),
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VulkanContext* context, VulkanResourceAllocator* allocator)
: mDevice(device),
mQueue(queue),
mBuffer(createCommandBuffer(device, pool)),
mFenceStatus(std::make_shared<VulkanCmdFence>(VK_INCOMPLETE)) {
mPool(createPool(mDevice, queueFamilyIndex)),
mContext(context),
mStorage(CAPACITY) {
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission);
for (auto& semaphore: mSubmissionSignals) {
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
}
VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence);
}
VulkanCommandBuffer::~VulkanCommandBuffer() {
vkDestroySemaphore(mDevice, mSubmission, VKALLOC);
vkDestroyFence(mDevice, mFence, VKALLOC);
}
void VulkanCommandBuffer::reset() noexcept {
mMarkerCount = 0;
mResources.clear();
mWaitSemaphores.clear();
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
mFenceStatus = std::make_shared<VulkanCmdFence>(VK_INCOMPLETE);
vkResetFences(mDevice, 1, &mFence);
}
void VulkanCommandBuffer::pushMarker(char const* marker) noexcept {
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = marker,
.color = {0, 1, 0, 1},
};
vkCmdBeginDebugUtilsLabelEXT(mBuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = marker,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerBeginEXT(mBuffer, &markerInfo);
for (auto& fence: mFences) {
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
}
mMarkerCount++;
}
void VulkanCommandBuffer::popMarker() noexcept{
assert_invariant(mMarkerCount > 0);
if (mContext->isDebugUtilsSupported()) {
vkCmdEndDebugUtilsLabelEXT(mBuffer);
} else if (mContext->isDebugMarkersSupported()) {
vkCmdDebugMarkerEndEXT(mBuffer);
for (size_t i = 0; i < CAPACITY; ++i) {
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator, mDevice, mPool);
}
mMarkerCount--;
#if !FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
(void) mContext;
#endif
}
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);
void VulkanCommands::terminate() {
wait();
gc();
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
for (VkSemaphore sema: mSubmissionSignals) {
vkDestroySemaphore(mDevice, sema, VKALLOC);
}
for (VkFence fence: mFences) {
vkDestroyFence(mDevice, fence, VKALLOC);
}
}
void VulkanCommandBuffer::begin() noexcept {
// Begin writing into the command buffer.
VkCommandBufferBeginInfo const binfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(mBuffer, &binfo);
}
VkSemaphore VulkanCommandBuffer::submit() {
while (mMarkerCount > 0) {
popMarker();
}
vkEndCommandBuffer(mBuffer);
VkPipelineStageFlags const waitDestStageMasks[2] = {
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
};
VkSubmitInfo submitInfo{
.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;
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 << "Submitting cmdbuffer=" << mBuffer
<< " wait=(";
for (size_t s = 0, count = mWaitSemaphores.size(); s < count; ++s) {
FVK_LOGI << mWaitSemaphores[s] << " ";
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]);
// 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,
};
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;
}
bool VulkanCommands::flush() {
// It's perfectly fine to call flush when no commands have been written.
if (mCurrentCommandBufferIndex < 0) {
return false;
}
// 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();
}
FVK_LOGI << ") "
<< " signal=" << mSubmission
<< " fence=" << mFence << utils::io::endl;
#endif
mFenceStatus->setStatus(VK_NOT_READY);
UTILS_UNUSED_IN_RELEASE VkResult result =
vkQueueSubmit(mQueue, 1, &submitInfo, mFence);
int8_t const index = mCurrentCommandBufferIndex;
VulkanCommandBuffer const* currentbuf = mStorage[index].get();
VkSemaphore const renderingFinished = mSubmissionSignals[index];
vkEndCommandBuffer(currentbuf->buffer());
// If the injected semaphore is an "image available" semaphore that has not yet been signaled,
// it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the
// VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage. However we need to assume the worst
// here and use VK_PIPELINE_STAGE_ALL_COMMANDS_BIT. This is a more aggressive stall, but it is
// the only safe option because the previously submitted command buffer might have set up some
// state that the new command buffer depends on.
VkPipelineStageFlags waitDestStageMasks[2] = {
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
};
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,
};
#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;
#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());
}
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
if (result != VK_SUCCESS) {
@@ -228,298 +309,176 @@ VkSemaphore VulkanCommandBuffer::submit() {
}
#endif
assert_invariant(result == VK_SUCCESS);
mWaitSemaphores.clear();
return mSubmission;
}
CommandBufferPool::CommandBufferPool(VulkanContext* context, VkDevice device, VkQueue queue,
uint8_t queueFamilyIndex, bool isProtected)
: mDevice(device),
mRecording(INVALID) {
VkCommandPoolCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT |
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
(isProtected ? VK_COMMAND_POOL_CREATE_PROTECTED_BIT : 0u),
.queueFamilyIndex = queueFamilyIndex,
};
vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool);
for (size_t i = 0; i < CAPACITY; ++i) {
mBuffers.emplace_back(
std::make_unique<VulkanCommandBuffer>(context, device, queue, mPool, isProtected));
}
}
CommandBufferPool::~CommandBufferPool() {
wait();
gc();
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
}
VulkanCommandBuffer& CommandBufferPool::getRecording() {
if (isRecording()) {
return *mBuffers[mRecording];
}
auto const findNext = [this]() {
for (int8_t i = 0; i < CAPACITY; ++i) {
if (!mSubmitted[i]) {
return i;
}
}
return INVALID;
};
while ((mRecording = findNext()) == INVALID) {
wait();
gc();
}
auto& recording = *mBuffers[mRecording];
recording.begin();
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
if (mGroupMarkers) {
std::unique_ptr<VulkanGroupMarkers> markers = std::make_unique<VulkanGroupMarkers>();
while (!mGroupMarkers->empty()) {
auto [marker, timestamp] = mGroupMarkers->pop_bottom();
recording.pushMarker(marker.c_str());
markers->push(marker, timestamp);
}
std::swap(mGroupMarkers, markers);
}
#endif
return recording;
}
void CommandBufferPool::gc() {
ActiveBuffers reclaimed;
mSubmitted.forEachSetBit([this,&reclaimed] (size_t index) {
auto& buffer = mBuffers[index];
if (buffer->getStatus() == VK_SUCCESS) {
reclaimed.set(index, true);
buffer->reset();
}
});
mSubmitted &= ~reclaimed;
}
void CommandBufferPool::update() {
mSubmitted.forEachSetBit([this] (size_t index) {
auto& buffer = mBuffers[index];
VkResult status = vkGetFenceStatus(mDevice, buffer->getVkFence());
if (status == VK_SUCCESS) {
buffer->setComplete();
}
});
}
VkSemaphore CommandBufferPool::flush() {
// We're not recording right now.
if (!isRecording()) {
return VK_NULL_HANDLE;
}
auto submitSemaphore = mBuffers[mRecording]->submit();
mSubmitted.set(mRecording, true);
mRecording = INVALID;
return submitSemaphore;
}
void CommandBufferPool::wait() {
uint8_t count = 0;
VkFence fences[CAPACITY];
mSubmitted.forEachSetBit([this, &count, &fences] (size_t index) {
fences[count++] = mBuffers[index]->getVkFence();
});
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
update();
}
void CommandBufferPool::waitFor(VkSemaphore previousAction) {
if (!isRecording()) {
return;
}
auto& recording = mBuffers[mRecording];
recording->insertWait(previousAction);
}
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::string CommandBufferPool::topMarker() const {
if (!mGroupMarkers || mGroupMarkers->empty()) {
return "";
}
return std::get<0>(mGroupMarkers->top());
}
void CommandBufferPool::pushMarker(char const* marker, VulkanGroupMarkers::Timestamp timestamp) {
if (!mGroupMarkers) {
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
}
mGroupMarkers->push(marker, timestamp);
getRecording().pushMarker(marker);
}
std::pair<std::string, VulkanGroupMarkers::Timestamp> CommandBufferPool::popMarker() {
assert_invariant(mGroupMarkers && !mGroupMarkers->empty());
auto ret = mGroupMarkers->pop();
// Note that if we're popping a marker while not recording, we would just pop the conceptual
// stack of marker (i.e. mGroupMarkers) and not carry out the pop on the command buffer.
if (isRecording()) {
getRecording().popMarker();
}
return ret;
}
void CommandBufferPool::insertEvent(char const* marker) {
getRecording().insertEvent(marker);
}
#endif // FVK_DEBUG_GROUP_MARKERS
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context)
: mDevice(device),
mProtectedQueue(protectedQueue),
mProtectedQueueFamilyIndex(protectedQueueFamilyIndex),
mContext(context),
mPool(std::make_unique<CommandBufferPool>(context, device, queue, queueFamilyIndex, false)) {}
void VulkanCommands::terminate() {
mPool.reset();
mProtectedPool.reset();
}
VulkanCommandBuffer& VulkanCommands::get() {
auto& ret = mPool->getRecording();
return ret;
}
VulkanCommandBuffer& VulkanCommands::getProtected() {
assert_invariant(mProtectedQueue != VK_NULL_HANDLE);
if (!mProtectedPool) {
mProtectedPool = std::make_unique<CommandBufferPool>(mContext, mDevice, mProtectedQueue,
mProtectedQueueFamilyIndex, true);
}
auto& ret = mProtectedPool->getRecording();
return ret;
}
bool VulkanCommands::flush() {
// It's possible to call flush and wait at "terminate", in which case, we'll just return.
if (!mPool && !mProtectedPool) {
return false;
}
VkSemaphore dependency = mInjectedDependency;
VkSemaphore lastSubmit = mLastSubmit;
bool hasFlushed = false;
// Note that we've ordered it so that the non-protected commands are followed by the protected
// commands. This assumes that the protected commands will be that one doing the rendering into
// the protected memory (i.e. protected render target).
for (auto pool: {mPool.get(), mProtectedPool.get()}) {
if (!pool || !pool->isRecording()) {
continue;
}
if (dependency != VK_NULL_HANDLE) {
pool->waitFor(dependency);
}
if (lastSubmit != VK_NULL_HANDLE) {
pool->waitFor(lastSubmit);
lastSubmit = VK_NULL_HANDLE;
}
dependency = pool->flush();
hasFlushed = true;
}
if (hasFlushed) {
mInjectedDependency = VK_NULL_HANDLE;
mLastSubmit = dependency;
}
mSubmissionSignal = renderingFinished;
mInjectedSignal = VK_NULL_HANDLE;
mCurrentCommandBufferIndex = -1;
return true;
}
VkSemaphore VulkanCommands::acquireFinishedSignal() {
VkSemaphore semaphore = mSubmissionSignal;
mSubmissionSignal = VK_NULL_HANDLE;
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
FVK_LOGI << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
#endif
return semaphore;
}
void VulkanCommands::injectDependency(VkSemaphore next) {
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
mInjectedSignal = next;
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
FVK_LOGI << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
#endif
}
void VulkanCommands::wait() {
// It's possible to call flush and wait at "terminate", in which case, we'll just return.
if (!mPool && !mProtectedPool) {
return;
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();
}
}
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("commands::wait");
mPool->wait();
if (mProtectedPool) {
mProtectedPool->wait();
if (count > 0) {
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
updateFences();
}
FVK_SYSTRACE_END();
}
void VulkanCommands::gc() {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("commands::gc");
mPool->gc();
if (mProtectedPool) {
mProtectedPool->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);
}
FVK_SYSTRACE_END();
}
void VulkanCommands::updateFences() {
mPool->update();
if (mProtectedPool) {
mProtectedPool->update();
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);
}
}
}
}
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp) {
mPool->pushMarker(str, timestamp);
if (mProtectedPool) {
mProtectedPool->pushMarker(str, timestamp);
}
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
FVK_LOGD << "----> " << str << utils::io::endl;
// If the timestamp is not 0, then we are carrying over a marker across buffer submits.
// If it is 0, then this is a normal marker push and we should just print debug line as usual.
if (timestamp.time_since_epoch().count() == 0.0) {
FVK_LOGD << "----> " << str << utils::io::endl;
}
#endif
// TODO: Add group marker color to the Driver API
VkCommandBuffer const cmdbuffer = get().buffer();
if (!mGroupMarkers) {
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
}
mGroupMarkers->push(str, timestamp);
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = str,
.color = {0, 1, 0, 1},
};
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = str,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo);
}
}
void VulkanCommands::popGroupMarker() {
assert_invariant(mGroupMarkers);
if (!mGroupMarkers->empty()) {
VkCommandBuffer const cmdbuffer = get().buffer();
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
auto ret = mPool->popMarker();
auto const& marker = ret.first;
auto const& startTime = ret.second;
auto const endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = endTime - startTime;
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
<< utils::io::endl;
auto const [marker, startTime] = mGroupMarkers->pop();
auto const endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = endTime - startTime;
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
<< utils::io::endl;
#else
mPool->popMarker();
#endif // FVK_DEBUG_PRINT_GROUP_MARKERS
if (mProtectedPool) {
mProtectedPool->popMarker();
mGroupMarkers->pop();
#endif
if (mContext->isDebugUtilsSupported()) {
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
} else if (mContext->isDebugMarkersSupported()) {
vkCmdDebugMarkerEndEXT(cmdbuffer);
}
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
// It could be that pop is called between flush() and get() (new command buffer), in which
// case the marker is in "carried over" state, we'd just remove that. Since the
// mCarriedOverMarkers is in the opposite order, we pop the bottom instead of the top.
mCarriedOverMarkers->pop_bottom();
}
}
void VulkanCommands::insertEventMarker(char const* str, uint32_t len) {
mPool->insertEvent(str);
if (mProtectedPool) {
mProtectedPool->insertEvent(str);
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);
}
}
std::string VulkanCommands::getTopGroupMarker() const {
if (mProtectedPool) {
return mProtectedPool->topMarker();
if (!mGroupMarkers || mGroupMarkers->empty()) {
return "";
}
return mPool->topMarker();
return std::get<0>(mGroupMarkers->top());
}
#endif // FVK_DEBUG_GROUP_MARKERS
+105 -110
View File
@@ -21,10 +21,8 @@
#include "DriverBase.h"
#include "VulkanAsyncHandles.h"
#include "VulkanConstants.h"
#include "VulkanUtility.h"
#include "vulkan/memory/ResourcePointer.h"
#include "VulkanResources.h"
#include <utils/Condition.h>
#include <utils/FixedCapacityVector.h>
@@ -39,8 +37,6 @@
namespace filament::backend {
using namespace fvkmemory;
struct VulkanContext;
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
@@ -51,122 +47,111 @@ public:
void push(std::string const& marker, Timestamp start = {}) noexcept;
std::pair<std::string, Timestamp> pop() noexcept;
std::pair<std::string, Timestamp> pop_bottom() noexcept;
std::pair<std::string, Timestamp> const& top() const;
std::pair<std::string, Timestamp> top() const;
bool empty() const noexcept;
private:
std::list<std::pair<std::string, Timestamp>> mMarkers;
std::list<std::string> mMarkers;
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
std::list<Timestamp> mTimestamps;
#endif
};
#endif // FVK_DEBUG_GROUP_MARKERS
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
struct VulkanCmdFence {
struct SetValueScope {
public:
~SetValueScope() {
mHolder->mutex.unlock();
mHolder->condition.notify_all();
}
private:
SetValueScope(VulkanCmdFence* fenceHolder, VkResult result) :
mHolder(fenceHolder) {
mHolder->mutex.lock();
mHolder->status.store(result);
}
VulkanCmdFence* mHolder;
friend struct VulkanCmdFence;
};
VulkanCmdFence(VkFence ifence);
~VulkanCmdFence() = default;
SetValueScope setValue(VkResult value) {
return {this, value};
}
VkFence& getFence() {
return fence;
}
VkResult getStatus() {
std::unique_lock<utils::Mutex> lock(mutex);
return status.load(std::memory_order_acquire);
}
private:
VkFence fence;
utils::Condition condition;
utils::Mutex mutex;
std::atomic<VkResult> status;
};
// The submission fence has shared ownership semantics because it is potentially wrapped by a
// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and
// we're done waiting on the most recent submission of the given command buffer.
struct VulkanCommandBuffer {
VulkanCommandBuffer(VulkanContext* mContext,
VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected);
VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, VkCommandPool pool);
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
~VulkanCommandBuffer();
inline void acquire(fvkmemory::resource_ptr<fvkmemory::Resource> resource) {
mResources.push_back(resource);
inline void acquire(VulkanResource* resource) {
mResourceManager.acquire(resource);
}
void reset() noexcept;
inline void insertWait(VkSemaphore sem) {
mWaitSemaphores.insert(sem);
inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) {
mResourceManager.acquireAll(srcResources);
}
void pushMarker(char const* marker) noexcept;
void popMarker() noexcept;
void insertEvent(char const* marker) noexcept;
void begin() noexcept;
VkSemaphore submit();
inline void setComplete() {
mFenceStatus->setStatus(VK_SUCCESS);
inline void reset() {
fence.reset();
mResourceManager.clear();
mPipeline = VK_NULL_HANDLE;
}
VkResult getStatus() {
return mFenceStatus->getStatus();
inline void setPipeline(VkPipeline pipeline) {
mPipeline = pipeline;
}
std::shared_ptr<VulkanCmdFence> getFenceStatus() const {
return mFenceStatus;
inline VkPipeline pipeline() const {
return mPipeline;
}
VkFence getVkFence() const {
return mFence;
inline VkCommandBuffer buffer() const {
if (fence) {
return mBuffer;
}
return VK_NULL_HANDLE;
}
VkCommandBuffer buffer() const {
return mBuffer;
}
std::shared_ptr<VulkanCmdFence> fence;
private:
VulkanContext* mContext;
uint8_t mMarkerCount;
bool const isProtected;
VkDevice mDevice;
VkQueue mQueue;
CappedArray<VkSemaphore, 2> mWaitSemaphores;
VulkanAcquireOnlyResourceManager mResourceManager;
VkCommandBuffer mBuffer;
VkSemaphore mSubmission;
VkFence mFence;
std::shared_ptr<VulkanCmdFence> mFenceStatus;
std::vector<fvkmemory::resource_ptr<Resource>> mResources;
VkPipeline mPipeline;
};
struct CommandBufferPool {
using ActiveBuffers = utils::bitset64;
static constexpr int8_t INVALID = -1;
CommandBufferPool(VulkanContext* context, VkDevice device, VkQueue queue,
uint8_t queueFamilyIndex, bool isProtected);
~CommandBufferPool();
VulkanCommandBuffer& getRecording();
void gc();
void update();
VkSemaphore flush();
void wait();
void waitFor(VkSemaphore previousAction);
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::string topMarker() const;
void pushMarker(char const* marker, VulkanGroupMarkers::Timestamp timestamp);
std::pair<std::string, VulkanGroupMarkers::Timestamp> popMarker();
void insertEvent(char const* marker);
#endif
inline bool isRecording() const { return mRecording != INVALID; }
private:
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
// int8 only goes up to 127, therefore capacity must be less than that.
static_assert(CAPACITY < 128);
// The number of bits in ActiveBuffers describe the usage of the buffers in the pool, so must be
// larger than the size of the pool.
static_assert(sizeof(ActiveBuffers) * 8 >= CAPACITY);
using BufferList = utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>>;
VkDevice mDevice;
VkCommandPool mPool;
ActiveBuffers mSubmitted;
std::vector<std::unique_ptr<VulkanCommandBuffer>> mBuffers;
int8_t mRecording;
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
#endif
// 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();
};
// Manages a set of command buffers and semaphores, exposing an API that is significantly simpler
@@ -180,6 +165,9 @@ private:
// - 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.
@@ -195,17 +183,13 @@ private:
class VulkanCommands {
public:
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context);
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();
@@ -213,17 +197,11 @@ 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 ret= mLastSubmit;
mLastSubmit = VK_NULL_HANDLE;
return ret;
}
VkSemaphore acquireFinishedSignal();
// 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) {
mInjectedDependency = next;
}
void injectDependency(VkSemaphore next);
// Destroys all command buffers that are no longer in use.
void gc();
@@ -234,25 +212,42 @@ 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 mProtectedQueue;
// For defered initialization if/when we need protected content
uint32_t const mProtectedQueueFamilyIndex;
VulkanContext* mContext;
VkQueue const mQueue;
VkCommandPool const mPool;
VulkanContext const* mContext;
std::unique_ptr<CommandBufferPool> mPool;
std::unique_ptr<CommandBufferPool> mProtectedPool;
// 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;
VkSemaphore mInjectedDependency = VK_NULL_HANDLE;
VkSemaphore mLastSubmit = VK_NULL_HANDLE;
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
std::unique_ptr<VulkanGroupMarkers> mCarriedOverMarkers;
#endif
};
} // namespace filament::backend
+13 -46
View File
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
@@ -63,7 +63,7 @@
#define FVK_DEBUG_SHADER_MODULE 0x00000800
#define FVK_DEBUG_READ_PIXELS 0x00001000
#define FVK_DEBUG_PIPELINE_CACHE 0x00002000
#define FVK_DEBUG_STAGING_ALLOCATION 0x00004000
#define FVK_DEBUG_ALLOCATION 0x00004000
// Enable the debug utils extension if it is available.
#define FVK_DEBUG_DEBUG_UTILS 0x00008000
@@ -77,12 +77,8 @@
// order of calls).
#define FVK_DEBUG_FORCE_LOG_TO_I 0x00020000
// Enable a minimal set of traces to assess the performance of the backend.
// All other debug features must be disabled.
#define FVK_DEBUG_PROFILING 0x00040000
// Useful default combinations
#define FVK_DEBUG_EVERYTHING (0xFFFFFFFF & ~FVK_DEBUG_PROFILING)
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
#define FVK_DEBUG_PERFORMANCE \
FVK_DEBUG_SYSTRACE
@@ -98,12 +94,6 @@
#define FVK_DEBUG_FLAGS 0
#endif
// Override the debug flags if we are forcing profiling mode
#if defined(FILAMENT_FORCE_PROFILING_MODE)
#undef FVK_DEBUG_FLAGS
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PROFILING)
#endif
#define FVK_ENABLED(flags) (((FVK_DEBUG_FLAGS) & (flags)) == (flags))
// Group marker only works only if validation or debug utils is enabled since it uses
@@ -122,10 +112,6 @@ static_assert(FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS));
static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
#endif
#if FVK_ENABLED(FVK_DEBUG_PROFILING) && FVK_DEBUG_FLAGS != FVK_DEBUG_PROFILING
#error PROFILING is exclusive; all other debug features must be disabled.
#endif
// end dependcy checks
// Shorthand for combination of enabled debug flags
@@ -137,34 +123,17 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
// end shorthands
#if FVK_DEBUG_FLAGS == FVK_DEBUG_PROFILING
#if FVK_ENABLED(FVK_DEBUG_SYSTRACE)
#ifndef NDEBUG
#error PROFILING is meaningless in DEBUG mode.
#endif
#define FVK_SYSTRACE_CONTEXT()
#define FVK_SYSTRACE_START(marker)
#define FVK_SYSTRACE_END()
#define FVK_SYSTRACE_SCOPE()
#define FVK_PROFILE_MARKER(marker) PROFILE_SCOPE(marker)
#elif FVK_ENABLED(FVK_DEBUG_SYSTRACE)
#include <utils/Systrace.h>
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
#define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__)
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
#include <utils/Systrace.h>
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
#else
#define FVK_SYSTRACE_CONTEXT()
#define FVK_SYSTRACE_START(marker)
#define FVK_SYSTRACE_END()
#define FVK_SYSTRACE_SCOPE()
#define FVK_PROFILE_MARKER(marker)
#define FVK_SYSTRACE_CONTEXT()
#define FVK_SYSTRACE_START(marker)
#define FVK_SYSTRACE_END()
#endif
#ifndef FVK_HANDLE_ARENA_SIZE_IN_MB
@@ -196,16 +165,14 @@ constexpr static const int FVK_REQUIRED_VERSION_MINOR = 1;
// buffers that have been submitted but have not yet finished rendering. Note that Filament can
// issue multiple commit calls in a single frame, and that we use a triple buffered swap chain on
// some platforms.
//
// Heuristic: Triple Buffering (3) multiplied by maximum number of renderpasses (15).
constexpr static const int FVK_MAX_COMMAND_BUFFERS = 3 * 15;
constexpr static const int FVK_MAX_COMMAND_BUFFERS = 10;
// Number of command buffer submissions that should occur before an unused pipeline is removed
// from the cache.
//
// If this number is low, VkPipeline construction will occur frequently, which can
// be extremely slow. If this number is high, the memory footprint will be large.
constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
constexpr static const int FVK_MAX_PIPELINE_AGE = 10;
// VulkanPipelineCache does not track which command buffers contain references to which pipelines,
// instead it simply waits for at least FVK_MAX_COMMAND_BUFFERS submissions to occur before
+12 -9
View File
@@ -25,11 +25,15 @@
#include <backend/PixelBufferDescriptor.h>
#include <utils/Panic.h>
#include <utils/FixedCapacityVector.h>
#include <algorithm> // for std::max
using namespace bluevk;
using utils::FixedCapacityVector;
namespace {
} // end anonymous namespace
@@ -82,7 +86,7 @@ VulkanTimestamps::VulkanTimestamps(VkDevice device) : mDevice(device) {
VkQueryPoolCreateInfo tqpCreateInfo = {
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
.queryType = VK_QUERY_TYPE_TIMESTAMP,
};
};
std::unique_lock<utils::Mutex> lock(mMutex);
tqpCreateInfo.queryCount = mUsed.size() * 2;
VkResult result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mPool);
@@ -97,7 +101,7 @@ std::tuple<uint32_t, uint32_t> VulkanTimestamps::getNextQuery() {
for (size_t timerIndex = 0; timerIndex < maxTimers; ++timerIndex) {
if (!mUsed.test(timerIndex)) {
mUsed.set(timerIndex);
return std::make_tuple(timerIndex * 2, timerIndex * 2 + 1);
return std::make_tuple(timerIndex * 2, timerIndex * 2 + 1);
}
}
FVK_LOGE << "More than " << maxTimers << " timers are not supported." << utils::io::endl;
@@ -109,7 +113,7 @@ void VulkanTimestamps::clearQuery(uint32_t queryIndex) {
}
void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
VulkanTimerQuery* query) {
uint32_t const index = query->getStartingQueryIndex();
auto const cmdbuffer = commands->buffer();
@@ -117,24 +121,23 @@ 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->getFenceStatus());
query->setFence(commands->fence);
}
void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
VulkanTimerQuery const* query) {
uint32_t const index = query->getStoppingQueryIndex();
vkCmdWriteTimestamp(commands->buffer(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
}
VulkanTimestamps::QueryResult VulkanTimestamps::getResult(
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
VulkanTimestamps::QueryResult VulkanTimestamps::getResult(VulkanTimerQuery const* query) {
uint32_t const index = query->getStartingQueryIndex();
QueryResult result;
size_t const dataSize = result.size() * sizeof(uint64_t);
VkDeviceSize const stride = sizeof(uint64_t) * 2;
VkResult vkresult =
vkGetQueryPoolResults(mDevice, mPool, index, 2, dataSize, (void*) result.data(), stride,
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
vkGetQueryPoolResults(mDevice, mPool, index, 2, dataSize, (void*) result.data(),
stride, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
FILAMENT_CHECK_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY)
<< "vkGetQueryPoolResults error: " << static_cast<int32_t>(vkresult);
if (vkresult == VK_NOT_READY) {
+14 -36
View File
@@ -21,8 +21,6 @@
#include "VulkanImageUtility.h"
#include "VulkanUtility.h"
#include "vulkan/memory/ResourcePointer.h"
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Mutex.h>
@@ -43,10 +41,11 @@ struct VulkanTimerQuery;
struct VulkanCommandBuffer;
struct VulkanAttachment {
fvkmemory::resource_ptr<VulkanTexture> texture;
VulkanTexture* texture = nullptr;
uint8_t level = 0;
uint8_t baseViewIndex = 0;
uint8_t layerCount = 1;
uint8_t layer = 0;
uint16_t layer = 0;
bool isDepth() const;
VkImage getImage() const;
@@ -72,11 +71,9 @@ public:
std::tuple<uint32_t, uint32_t> getNextQuery();
void clearQuery(uint32_t queryIndex);
void beginQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query);
void endQuery(VulkanCommandBuffer const* commands,
fvkmemory::resource_ptr<VulkanTimerQuery> query);
QueryResult getResult(fvkmemory::resource_ptr<VulkanTimerQuery> query);
void beginQuery(VulkanCommandBuffer const* commands, VulkanTimerQuery* query);
void endQuery(VulkanCommandBuffer const* commands, VulkanTimerQuery const* query);
QueryResult getResult(VulkanTimerQuery const* query);
private:
VkDevice mDevice;
@@ -86,9 +83,7 @@ private:
};
struct VulkanRenderPass {
// Between the begin and end command render pass we cache the command buffer
VulkanCommandBuffer* commandBuffer;
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget;
VulkanRenderTarget* renderTarget;
VkRenderPass renderPass;
RenderPassParams params;
int currentSubpass;
@@ -99,9 +94,6 @@ struct VulkanRenderPass {
struct VulkanContext {
public:
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
assert_invariant(isProtectedMemorySupported() == true);
}
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
if (flags & 1) {
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
@@ -122,19 +114,19 @@ public:
}
inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept {
return mPhysicalDeviceProperties.properties.limits;
return mPhysicalDeviceProperties.limits;
}
inline uint32_t getPhysicalDeviceVendorId() const noexcept {
return mPhysicalDeviceProperties.properties.vendorID;
return mPhysicalDeviceProperties.vendorID;
}
inline bool isImageCubeArraySupported() const noexcept {
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
}
inline bool isDepthClampSupported() const noexcept {
return mPhysicalDeviceFeatures.features.depthClamp == VK_TRUE;
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
}
inline bool isDebugMarkersSupported() const noexcept {
@@ -150,30 +142,16 @@ public:
}
inline bool isClipDistanceSupported() const noexcept {
return mPhysicalDeviceFeatures.features.shaderClipDistance == VK_TRUE;
}
inline bool isLazilyAllocatedMemorySupported() const noexcept {
return mLazilyAllocatedMemorySupported;
}
inline bool isProtectedMemorySupported() const noexcept {
return mProtectedMemorySupported;
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2
};
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2
};
VkPhysicalDeviceProperties mPhysicalDeviceProperties = {};
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures = {};
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mMultiviewEnabled = false;
bool mLazilyAllocatedMemorySupported = false;
bool mProtectedMemorySupported = false;
VkFormatList mDepthStencilFormats;
VkFormatList mBlittableDepthStencilFormats;
File diff suppressed because it is too large Load Diff
+33 -18
View File
@@ -24,14 +24,13 @@
#include "VulkanHandles.h"
#include "VulkanPipelineCache.h"
#include "VulkanReadPixels.h"
#include "VulkanResourceAllocator.h"
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanUtility.h"
#include "backend/DriverEnums.h"
#include "caching/VulkanDescriptorSetManager.h"
#include "caching/VulkanPipelineLayoutCache.h"
#include "memory/ResourceManager.h"
#include "memory/ResourcePointer.h"
#include "DriverBase.h"
#include "private/backend/Driver.h"
@@ -48,6 +47,21 @@ 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,
@@ -78,9 +92,6 @@ public:
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
private:
template<typename D>
using resource_ptr = fvkmemory::resource_ptr<D>;
static constexpr uint8_t MAX_SAMPLER_BINDING_COUNT = Program::SAMPLER_BINDING_COUNT;
void debugCommandBegin(CommandStream* cmds, bool synchronous,
@@ -117,16 +128,25 @@ private:
void collectGarbage();
VulkanPlatform* mPlatform = nullptr;
fvkmemory::ResourceManager mResourceManager;
std::unique_ptr<VulkanTimestamps> mTimestamps;
resource_ptr<VulkanSwapChain> mCurrentSwapChain;
resource_ptr<VulkanRenderTarget> mDefaultRenderTarget;
// Placeholder resources
VulkanTexture* mEmptyTexture;
VulkanBufferObject* mEmptyBufferObject;
VulkanSwapChain* mCurrentSwapChain = nullptr;
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
VulkanRenderPass mCurrentRenderPass = {};
VmaAllocator mAllocator = VK_NULL_HANDLE;
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
VulkanContext mContext = {};
VulkanResourceAllocator mResourceAllocator;
VulkanResourceManager mResourceManager;
// Used for resources that are created synchronously and used and destroyed on the backend
// thread.
VulkanThreadSafeResourceManager mThreadSafeResourceManager;
VulkanCommands mCommands;
VulkanPipelineLayoutCache mPipelineLayoutCache;
@@ -140,18 +160,13 @@ private:
VulkanDescriptorSetManager mDescriptorSetManager;
// This is necessary for us to write to push constants after binding a pipeline.
struct {
resource_ptr<VulkanProgram> program;
struct BoundPipeline {
VulkanProgram* program;
VkPipelineLayout pipelineLayout;
DescriptorSetMask descriptorSetMask;
} 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 = {};
};
BoundPipeline mBoundPipeline = {};
RenderPassFboBundle mRenderPassFboInfo;
bool const mIsSRGBSwapChainSupported;
backend::StereoscopicType const mStereoscopicType;
@@ -31,6 +31,7 @@ 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;
@@ -41,7 +42,6 @@ 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 const& config) noexcept {
FboMap::iterator iter = mFramebufferCache.find(config);
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
auto 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 const& config) noexcept {
return framebuffer;
}
VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept {
VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
auto iter = mRenderPassCache.find(config);
if (UTILS_LIKELY(iter != mRenderPassCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
iter.value().timestamp = mCurrentTime;
@@ -255,10 +255,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
.format = config.colorFormat[i],
.samples = (VkSampleCountFlagBits) config.samples,
.loadOp = clear ? kClear : (discard ? kDontCare : kKeep),
.storeOp = (config.usesLazilyAllocatedMemory & (1 << i)) ? kDisableStore : kEnableStore,
.storeOp = kEnableStore,
.stencilLoadOp = kDontCare,
.stencilStoreOp = kDisableStore,
.initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
.initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear)
? imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT)
: imgutil::getVkLayout(VulkanLayout::UNDEFINED),
.finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT),
};
}
@@ -362,7 +364,7 @@ void VulkanFboCache::gc() noexcept {
}
const uint32_t evictTime = mCurrentTime - TIME_BEFORE_EVICTION;
for (FboMap::iterator iter = mFramebufferCache.begin(); iter != mFramebufferCache.end(); ++iter) {
for (auto iter = mFramebufferCache.begin(); iter != mFramebufferCache.end(); ++iter) {
const FboVal fbo = iter->second;
if (fbo.timestamp < evictTime && fbo.handle) {
mRenderPassRefCount[iter->first.renderPass]--;
+18 -9
View File
@@ -42,25 +42,36 @@ 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.");
@@ -98,10 +109,10 @@ public:
~VulkanFboCache();
// Retrieves or creates a VkFramebuffer handle.
VkFramebuffer getFramebuffer(FboKey const& config) noexcept;
VkFramebuffer getFramebuffer(FboKey config) noexcept;
// Retrieves or creates a VkRenderPass handle.
VkRenderPass getRenderPass(RenderPassKey const& config) noexcept;
VkRenderPass getRenderPass(RenderPassKey config) noexcept;
// Evicts old unused Vulkan objects. Call this once per frame.
void gc() noexcept;
@@ -111,9 +122,7 @@ public:
private:
VkDevice mDevice;
using FboMap = tsl::robin_map<FboKey, FboVal, FboKeyHashFn, FboKeyEqualFn>;
FboMap mFramebufferCache;
tsl::robin_map<FboKey, FboVal, FboKeyHashFn, FboKeyEqualFn> mFramebufferCache;
tsl::robin_map<RenderPassKey, RenderPassVal, RenderPassHash, RenderPassEq> mRenderPassCache;
tsl::robin_map<VkRenderPass, uint32_t> mRenderPassRefCount;
uint32_t mCurrentTime = 0;
+232 -238
View File
@@ -17,13 +17,9 @@
#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"
#include "vulkan/memory/ResourcePointer.h"
#include "spirv/VulkanSpirvUtils.h"
#include <backend/platforms/VulkanPlatform.h>
@@ -36,12 +32,15 @@ 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);
@@ -63,6 +62,13 @@ 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:
@@ -74,6 +80,61 @@ 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;
@@ -87,8 +148,6 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
}
break;
}
// TODO: properly handle external sampler
case DescriptorType::SAMPLER_EXTERNAL:
case DescriptorType::SAMPLER: {
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
break;
@@ -105,40 +164,28 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
return mask;
}
fvkmemory::resource_ptr<VulkanTexture> initMsaaTexture(
fvkmemory::resource_ptr<VulkanTexture> texture, VkDevice device,
VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands, fvkmemory::ResourceManager* resManager, uint8_t levels,
uint8_t samples, VulkanStagePool& stagePool) {
assert_invariant(texture);
auto msTexture = texture->getSidecar();
if (UTILS_UNLIKELY(!msTexture)) {
// Clear all usage flags that are not related to attachments, so that we can
// use the transient usage flag.
const TextureUsage usage = texture->usage & TextureUsage::ALL_ATTACHMENTS;
assert_invariant(static_cast<uint16_t>(usage) != 0U);
msTexture = resource_ptr<VulkanTexture>::construct(resManager, device, physicalDevice,
context, allocator, resManager, commands, texture->target, levels, texture->format,
samples, texture->width, texture->height, texture->depth, usage, stagePool);
texture->setSidecar(msTexture);
}
return msTexture;
}
} // anonymous namespace
void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanTexture> texture) {
mResources.push_back(texture);
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device,
DescriptorSetLayout const& layout)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), mDevice(device),
vklayout(createDescriptorSetLayout(device, getLayoutCreateInfo(layout))),
bitmask(fromBackendLayout(layout)),
count(Count::fromLayoutBitmask(bitmask)) {
}
void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanBufferObject> obj) {
mResources.push_back(obj);
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
}
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout)
: bitmask(fromBackendLayout(layout)),
count(Count::fromLayoutBitmask(bitmask)) {}
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
mResources.acquire(texture);
mTextures[mTextureCount++] = texture;
}
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
mResources.acquire(bufferObject);
}
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
mRangeCount = 0;
@@ -163,9 +210,9 @@ PushConstantDescription::PushConstantDescription(backend::Program const& program
}
}
void PushConstantDescription::write(VkCommandBuffer cmdbuf, VkPipelineLayout layout,
void PushConstantDescription::write(VulkanCommands* commands, 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)) {
@@ -181,12 +228,13 @@ void PushConstantDescription::write(VkCommandBuffer cmdbuf, VkPipelineLayout lay
int const ival = std::get<int>(value);
binaryValue = *reinterpret_cast<uint32_t const*>(&ival);
}
vkCmdPushConstants(cmdbuf, layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE,
vkCmdPushConstants(cmdbuf->buffer(), layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE,
&binaryValue);
}
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
: HwProgram(builder.getName()),
VulkanResource(VulkanResourceType::PROGRAM),
mInfo(new(std::nothrow) PipelineInfo(builder)),
mDevice(device) {
@@ -252,239 +300,153 @@ VulkanProgram::~VulkanProgram() {
}
// Creates a special "default" render target (i.e. associated with the swap chain)
VulkanRenderTarget::VulkanRenderTarget()
: HwRenderTarget(0, 0),
mOffscreen(false),
mProtected(false),
mInfo(std::make_unique<Auxiliary>()) {
mInfo->rpkey.samples = mInfo->fbkey.samples = 1;
}
VulkanRenderTarget::VulkanRenderTarget() :
HwRenderTarget(0, 0),
VulkanResource(VulkanResourceType::RENDER_TARGET),
mOffscreen(false), mSamples(1) {}
VulkanRenderTarget::~VulkanRenderTarget() = default;
void VulkanRenderTarget::bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain) {
void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
assert_invariant(!mOffscreen);
VkExtent2D const extent = swapchain->getExtent();
VkExtent2D const extent = swapChain.getExtent();
mColor[0] = { .texture = swapChain.getCurrentColor() };
mDepth = { .texture = swapChain.getDepth() };
width = extent.width;
height = extent.height;
mProtected = swapchain->isProtected();
VulkanAttachment color = {};
color.texture = swapchain->getCurrentColor();
mInfo->attachments = {color};
auto& fbkey = mInfo->fbkey;
auto& rpkey = mInfo->rpkey;
rpkey.colorFormat[0] = color.getFormat();
fbkey.width = width;
fbkey.height = height;
fbkey.color[0] = color.getImageView();
fbkey.resolve[0] = VK_NULL_HANDLE;
if (swapchain->getDepth()) {
VulkanAttachment depth = {};
depth.texture = swapchain->getDepth();
mInfo->attachments.push_back(depth);
mInfo->depthIndex = 1;
rpkey.depthFormat = depth.getFormat();
fbkey.depth = depth.getImageView();
} else {
rpkey.depthFormat = VK_FORMAT_UNDEFINED;
fbkey.depth = VK_NULL_HANDLE;
}
mInfo->colors.set(0);
}
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, fvkmemory::ResourceManager* resourceManager,
VmaAllocator allocator, VulkanCommands* commands, uint32_t width, uint32_t height,
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
uint32_t width, uint32_t height, uint8_t samples,
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
: HwRenderTarget(width, height),
VulkanResource(VulkanResourceType::RENDER_TARGET),
mOffscreen(true),
mProtected(false),
mInfo(std::make_unique<Auxiliary>()) {
auto& depth = depthStencil[0];
mSamples(samples),
mLayerCount(layerCount) {
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
mColor[index] = color[index];
}
mDepth = depthStencil[0];
VulkanTexture* depthTexture = (VulkanTexture*) mDepth.texture;
if (samples == 1) {
return;
}
// 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();
samples = reduceSampleCount(samples, limits.framebufferDepthSampleCounts &
mSamples = samples = reduceSampleCount(samples, limits.framebufferDepthSampleCounts &
limits.framebufferColorSampleCounts);
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;
// Create sidecar MSAA textures for color attachments if they don't already exist.
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
VulkanAttachment& attachment = color[index];
auto texture = attachment.texture;
if (!texture) {
rpkey.colorFormat[index] = VK_FORMAT_UNDEFINED;
continue;
}
mProtected |= texture->getIsProtected();
attachments.push_back(attachment);
mInfo->colors.set(index);
rpkey.colorFormat[index] = attachment.getFormat();
fbkey.color[index] = attachment.getImageView();
fbkey.resolve[index] = VK_NULL_HANDLE;
if (samples > 1) {
VulkanAttachment msaaAttachment = {};
if (texture->samples == 1) {
auto msaaTexture = initMsaaTexture(texture, device, physicalDevice, context,
allocator, commands, resourceManager, texture->levels, samples, stagePool);
if (msaaTexture && msaaTexture->isTransientAttachment()) {
rpkey.usesLazilyAllocatedMemory |= (1 << index);
}
if (attachment.texture->samples == 1) {
rpkey.needsResolveMask |= (1 << index);
}
msaaAttachment = {
.texture = msaaTexture,
.layerCount = layerCount,
};
fbkey.resolve[index] = attachment.getImageView();
} else {
msaaAttachment = {
.texture = texture,
.layerCount = layerCount,
};
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);
}
fbkey.color[index] = msaaAttachment.getImageView();
msaa.push_back(msaaAttachment);
mMsaaAttachments[index] = {.texture = msTexture};
}
if (texture && texture->samples > 1) {
mMsaaAttachments[index] = mColor[index];
}
}
if (attachments.size() > 0 && samples > 1 && msaa.size() > 0) {
mInfo->msaaIndex = (uint8_t) attachments.size();
attachments.insert(attachments.end(), msaa.begin(), msaa.end());
if (!depthTexture) {
return;
}
if (depth.texture) {
auto depthTexture = depth.texture;
mInfo->depthIndex = (uint8_t) attachments.size();
attachments.push_back(depth);
fbkey.depth = depth.getImageView();
if (samples > 1) {
mInfo->msaaDepthIndex = mInfo->depthIndex;
if (depthTexture->samples == 1) {
// MSAA depth texture must have the mipmap count of 1
uint8_t const msLevel = 1;
// Create sidecar MSAA texture for the depth attachment if it does not already
// exist.
auto msaa = initMsaaTexture(depthTexture, device, physicalDevice, context,
allocator, commands, resourceManager, msLevel, samples, stagePool);
mInfo->msaaDepthIndex = (uint8_t) attachments.size();
attachments.push_back({ .texture = msaa, .layerCount = layerCount });
}
}
// There is no need for sidecar depth if the depth texture is already MSAA.
if (depthTexture->samples > 1) {
mMsaaDepthAttachment = mDepth;
return;
}
// 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 {
auto const& extent = getExtent();
const auto& extent = getExtent();
flipVertically(bounds, extent.height);
clampToFramebuffer(bounds, extent.width, extent.height);
}
void VulkanRenderTarget::transformViewportToPlatform(VkViewport* bounds) const {
void VulkanRenderTarget::transformClientRectToPlatform(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;
mInfo->colors.forEachSetBit([&count, &pass](size_t index) {
if (!(pass.params.subpassMask & (1 << index))) {
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) {
count++;
}
});
}
return count;
}
void VulkanRenderTarget::emitBarriersBeginRenderPass(VulkanCommandBuffer& commands) {
auto& attachments = mInfo->attachments;
auto samples = mInfo->fbkey.samples;
auto barrier = [&commands](VulkanAttachment& attachment, VulkanLayout const layout) {
auto tex = attachment.texture;
auto const& range = attachment.getSubresourceRange();
if (tex->getLayout(range.baseMipLevel, range.baseArrayLayer) != layout &&
!tex->transitionLayout(&commands, range, layout)) {
// If the layout transition did not emit a barrier, we do it manually here.
tex->samplerToAttachmentBarrier(&commands, range);
}
};
for (size_t i = 0, count = mInfo->colors.count(); i < count; ++i) {
auto& attachment = attachments[i];
auto tex = attachment.texture;
if (samples == 1 || tex->samples == 1) {
barrier(attachment, VulkanLayout::COLOR_ATTACHMENT);
}
}
if (mInfo->msaaIndex != Auxiliary::UNDEFINED_INDEX) {
for (size_t i = mInfo->msaaIndex, count = mInfo->msaaIndex + mInfo->colors.count();
i < count; ++i) {
barrier(attachments[i], VulkanLayout::COLOR_ATTACHMENT);
}
}
if (mInfo->depthIndex != Auxiliary::UNDEFINED_INDEX) {
barrier(attachments[mInfo->depthIndex], VulkanLayout::DEPTH_ATTACHMENT);
}
if (mInfo->msaaDepthIndex != Auxiliary::UNDEFINED_INDEX) {
barrier(attachments[mInfo->msaaDepthIndex], VulkanLayout::DEPTH_ATTACHMENT);
}
}
void VulkanRenderTarget::emitBarriersEndRenderPass(VulkanCommandBuffer& commands) {
if (isSwapChain()) {
return;
}
for (auto& attachment: mInfo->attachments) {
auto const& range = attachment.getSubresourceRange();
bool const isDepth = attachment.isDepth();
auto texture = attachment.texture;
if (isDepth) {
texture->setLayout(range, VulkanFboCache::FINAL_DEPTH_ATTACHMENT_LAYOUT);
if (!texture->transitionLayout(&commands, range, VulkanLayout::DEPTH_SAMPLER)) {
texture->attachmentToSamplerBarrier(&commands, range);
}
} else {
texture->setLayout(range, VulkanFboCache::FINAL_COLOR_ATTACHMENT_LAYOUT);
if (!texture->transitionLayout(&commands, range, VulkanLayout::READ_WRITE)) {
texture->attachmentToSamplerBarrier(&commands, range);
}
}
}
}
VulkanVertexBufferInfo::VulkanVertexBufferInfo(
uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes)
: HwVertexBufferInfo(bufferCount, attributeCount),
VulkanResource(VulkanResourceType::VERTEX_BUFFER_INFO),
mInfo(attributes.size()) {
auto attribDesc = mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
auto bufferDesc = mInfo.mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
auto offsets = mInfo.mSoa.data<PipelineInfo::OFFSETS>();
@@ -522,15 +484,19 @@ VulkanVertexBufferInfo::VulkanVertexBufferInfo(
}
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
uint32_t vertexCount, fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi)
VulkanResourceAllocator* allocator,
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih)
: HwVertexBuffer(vertexCount),
vbi(vbi),
// TODO: Seems a bit wasteful. can we do better here?
mBuffers(MAX_VERTEX_BUFFER_COUNT) {
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
vbih(vbih),
mBuffers(MAX_VERTEX_BUFFER_COUNT), // TODO: can we do better here?
mResources(allocator) {
}
void VulkanVertexBuffer::setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
uint32_t index) {
void VulkanVertexBuffer::setBuffer(VulkanResourceAllocator const& allocator,
VulkanBufferObject* bufferObject, uint32_t index) {
VulkanVertexBufferInfo const* const vbi =
const_cast<VulkanResourceAllocator&>(allocator).handle_cast<VulkanVertexBufferInfo*>(vbih);
size_t const count = vbi->getAttributeCount();
VkBuffer* const vkbuffers = getVkBuffers();
int8_t const* const attribToBuffer = vbi->getAttributeToBuffer();
@@ -539,20 +505,48 @@ void VulkanVertexBuffer::setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> b
vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer();
}
}
mResources.push_back(bufferObject);
mResources.acquire(bufferObject);
}
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
uint32_t byteCount, BufferObjectBinding bindingType)
: HwBufferObject(byteCount),
VulkanResource(VulkanResourceType::BUFFER_OBJECT),
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
bindingType(bindingType) {}
VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
fvkmemory::resource_ptr<VulkanVertexBuffer> vb,
fvkmemory::resource_ptr<VulkanIndexBuffer> ib)
: HwRenderPrimitive{.type = pt},
vertexBuffer(vb),
indexBuffer(ib) {}
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
: VulkanThreadSafeResource(VulkanResourceType::TIMER_QUERY),
mStartingQueryIndex(std::get<0>(indices)),
mStoppingQueryIndex(std::get<1>(indices)) {}
void VulkanTimerQuery::setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
std::unique_lock<utils::Mutex> lock(mFenceMutex);
mFence = fence;
}
bool VulkanTimerQuery::isCompleted() noexcept {
std::unique_lock<utils::Mutex> lock(mFenceMutex);
// QueryValue is a synchronous call and might occur before beginTimerQuery has written anything
// into the command buffer, which is an error according to the validation layer that ships in
// the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to require that the
// timestamp has at least been written into a processed command buffer.
// This fence indicates that the corresponding buffer has been completed.
return mFence && mFence->getStatus() == VK_SUCCESS;
}
VulkanTimerQuery::~VulkanTimerQuery() = default;
VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh)
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
mResources(resourceAllocator) {
type = pt;
vertexBuffer = resourceAllocator->handle_cast<VulkanVertexBuffer*>(vbh);
indexBuffer = resourceAllocator->handle_cast<VulkanIndexBuffer*>(ibh);
mResources.acquire(vertexBuffer);
mResources.acquire(indexBuffer);
}
} // namespace filament::backend
+165 -129
View File
@@ -20,14 +20,13 @@
// This needs to be at the top
#include "DriverBase.h"
#include "VulkanAsyncHandles.h"
#include "VulkanBuffer.h"
#include "VulkanFboCache.h"
#include "VulkanResources.h"
#include "VulkanSwapChain.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
#include "vulkan/memory/Resource.h"
#include <private/backend/SamplerGroup.h>
#include <backend/Program.h>
#include <utils/bitset.h>
@@ -35,8 +34,6 @@
#include <utils/Mutex.h>
#include <utils/StructureOfArrays.h>
#include <array>
namespace filament::backend {
namespace {
@@ -44,11 +41,11 @@ namespace {
template<typename Bitmask>
inline uint8_t collapsedCount(Bitmask const& mask) {
static_assert(sizeof(mask) <= 64);
constexpr uint64_t VERTEX_MASK = (1ULL << getFragmentStageShift<Bitmask>()) - 1ULL;
constexpr uint64_t FRAGMENT_MASK = (VERTEX_MASK << getFragmentStageShift<Bitmask>());
constexpr uint64_t VERTEX_MASK = (1ULL << getVertexStageShift<Bitmask>()) - 1ULL;
constexpr uint64_t FRAGMENT_MASK = (1ULL << getFragmentStageShift<Bitmask>()) - 1ULL;
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();
}
@@ -57,7 +54,7 @@ inline uint8_t collapsedCount(Bitmask const& mask) {
class VulkanTimestamps;
struct VulkanBufferObject;
struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Resource {
struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout {
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 4;
static constexpr uint8_t MAX_BINDINGS = 25;
@@ -117,59 +114,51 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
}
};
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
VulkanDescriptorSetLayout(VkDevice device, DescriptorSetLayout const& layout);
~VulkanDescriptorSetLayout() = default;
VkDescriptorSetLayout getVkLayout() const { return mVkLayout; }
void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; }
~VulkanDescriptorSetLayout();
VkDevice const mDevice;
VkDescriptorSetLayout const vklayout;
Bitmask const bitmask;
Count const count;
private:
VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE;
};
struct VulkanDescriptorSet : public HwDescriptorSet, fvkmemory::Resource {
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(VulkanDescriptorSet*)>;
using OnRecycle = std::function<void()>;
VulkanDescriptorSet(VkDescriptorSet rawSet,
UniformBufferBitmask const& dynamicUboMask,
uint8_t uniqueDynamicUboCount,
VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
OnRecycle&& onRecycleFn)
: vkSet(rawSet),
dynamicUboMask(dynamicUboMask),
uniqueDynamicUboCount(uniqueDynamicUboCount),
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
vkSet(rawSet),
mResources(allocator),
mOnRecycleFn(std::move(onRecycleFn)) {}
~VulkanDescriptorSet() {
if (mOnRecycleFn) {
mOnRecycleFn(this);
mOnRecycleFn();
}
}
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets);
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; });
}
backend::DescriptorSetOffsetArray const* getOffsets() {
return &mOffsets;
}
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
// TODO: maybe change to fixed size for performance.
VkDescriptorSet const vkSet;
UniformBufferBitmask const dynamicUboMask;
uint8_t const uniqueDynamicUboCount;
private:
backend::DescriptorSetOffsetArray mOffsets;
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
std::array<VulkanTexture*, 16> mTextures = { nullptr };
uint8_t mTextureCount = 0;
VulkanAcquireOnlyResourceManager mResources;
OnRecycle mOnRecycleFn;
};
@@ -177,11 +166,14 @@ using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>;
struct PushConstantDescription {
explicit PushConstantDescription(backend::Program const& program) noexcept;
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
void write(VkCommandBuffer cmdbuf, VkPipelineLayout layout, backend::ShaderStage stage,
void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage,
uint8_t index, backend::PushConstantVariant const& value);
private:
@@ -192,10 +184,12 @@ private:
uint32_t mRangeCount;
};
struct VulkanProgram : public HwProgram, fvkmemory::Resource {
struct VulkanProgram : public HwProgram, VulkanResource {
using BindingList = CappedArray<uint16_t, MAX_SAMPLER_COUNT>;
VulkanProgram(VkDevice device, Program const& builder) noexcept;
~VulkanProgram();
inline VkShaderModule getVertexShader() const {
@@ -204,6 +198,14 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
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();
}
@@ -212,9 +214,9 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
return mInfo->pushConstantDescription.getVkRanges();
}
inline void writePushConstant(VkCommandBuffer cmdbuf, VkPipelineLayout layout,
inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout,
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
mInfo->pushConstantDescription.write(commands, layout, stage, index, value);
}
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
@@ -230,11 +232,26 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
private:
struct PipelineInfo {
explicit PipelineInfo(backend::Program const& program) noexcept
: pushConstantDescription(program)
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
pushConstantDescription(program)
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
, bindingToName(MAX_SAMPLER_COUNT, "")
#endif
{}
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;
@@ -249,88 +266,47 @@ private:
//
// We use private inheritance to shield clients from the width / height fields in HwRenderTarget,
// which are not representative when this is the default render target.
struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
// Creates an offscreen render target.
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, fvkmemory::ResourceManager* resourceManager,
VmaAllocator allocator, VulkanCommands* commands, uint32_t width, uint32_t height,
VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
uint32_t width, uint32_t height,
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount);
~VulkanRenderTarget();
// Creates a special "default" render target (i.e. associated with the swap chain)
explicit VulkanRenderTarget();
void transformClientRectToPlatform(VkRect2D* bounds) const;
void transformViewportToPlatform(VkViewport* bounds) const;
inline VkExtent2D getExtent() const {
return {width, height};
}
inline VulkanAttachment& getColor0() const {
assert_invariant(mInfo->colors[0]);
return mInfo->attachments[0];
}
inline VulkanAttachment& getDepth() const {
assert_invariant(hasDepth());
if (mInfo->fbkey.samples == 1) {
return mInfo->attachments[mInfo->depthIndex];
}
return mInfo->attachments[mInfo->msaaDepthIndex];
}
inline VulkanFboCache::RenderPassKey const& getRenderPassKey() const {
return mInfo->rpkey;
}
inline VulkanFboCache::FboKey const& getFboKey() const {
return mInfo->fbkey;
}
inline uint8_t getSamples() const {
return mInfo->fbkey.samples;
}
uint8_t getColorTargetCount(VulkanRenderPass const& pass) const;
inline bool hasDepth() const { return mInfo->depthIndex != Auxiliary::UNDEFINED_INDEX; }
inline bool isSwapChain() const { return !mOffscreen; }
inline bool isProtected() const { return mProtected; }
void bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain);
void emitBarriersBeginRenderPass(VulkanCommandBuffer& commands);
void emitBarriersEndRenderPass(VulkanCommandBuffer& commands);
void transformClientRectToPlatform(VkViewport* bounds) const;
VkExtent2D getExtent() const;
// We return references in the following methods to avoid a copy.
VulkanAttachment& getColor(int target);
VulkanAttachment& getMsaaColor(int target);
VulkanAttachment& getDepth();
VulkanAttachment& getMsaaDepth();
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
uint8_t getSamples() const { return mSamples; }
uint8_t getLayerCount() const { return mLayerCount; }
bool hasDepth() const { return mDepth.texture; }
bool isSwapChain() const { return !mOffscreen; }
void bindToSwapChain(VulkanSwapChain& surf);
private:
struct Auxiliary {
static constexpr int8_t UNDEFINED_INDEX = -1;
explicit Auxiliary() noexcept = default;
VulkanFboCache::RenderPassKey rpkey = {};
VulkanFboCache::FboKey fbkey = {};
std::vector<VulkanAttachment> attachments;
utils::bitset32 colors;
int8_t depthIndex = UNDEFINED_INDEX;
int8_t msaaDepthIndex = UNDEFINED_INDEX;
int8_t msaaIndex = UNDEFINED_INDEX;
};
bool const mOffscreen;
bool mProtected;
std::unique_ptr<Auxiliary> mInfo;
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 VulkanBufferObject;
struct VulkanVertexBufferInfo : public HwVertexBufferInfo, fvkmemory::Resource {
struct VulkanVertexBufferInfo : public HwVertexBufferInfo, VulkanResource {
VulkanVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes);
@@ -377,24 +353,33 @@ private:
PipelineInfo mInfo;
};
struct VulkanVertexBuffer : public HwVertexBuffer, fvkmemory::Resource {
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, uint32_t vertexCount,
fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi);
void setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> bufferObject, uint32_t index);
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
VulkanResourceAllocator* allocator,
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih);
inline VkBuffer const* getVkBuffers() const { return mBuffers.data(); }
inline VkBuffer* getVkBuffers() { return mBuffers.data(); }
fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi;
void setBuffer(VulkanResourceAllocator const& allocator,
VulkanBufferObject* bufferObject, uint32_t index);
inline VkBuffer const* getVkBuffers() const {
return mBuffers.data();
}
inline VkBuffer* getVkBuffers() {
return mBuffers.data();
}
Handle<HwVertexBufferInfo> vbih;
private:
utils::FixedCapacityVector<VkBuffer> mBuffers;
std::vector<fvkmemory::resource_ptr<VulkanBufferObject>> mResources;
FixedSizeVulkanResourceManager<MAX_VERTEX_BUFFER_COUNT> mResources;
};
struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource {
struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, uint8_t elementSize,
uint32_t indexCount)
: HwIndexBuffer(elementSize, indexCount),
VulkanResource(VulkanResourceType::INDEX_BUFFER),
buffer(allocator, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, elementSize * indexCount),
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
@@ -402,7 +387,7 @@ struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource {
const VkIndexType indexType;
};
struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource {
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
BufferObjectBinding bindingType);
@@ -410,15 +395,66 @@ struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource {
const BufferObjectBinding bindingType;
};
struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
VulkanRenderPrimitive(PrimitiveType pt, fvkmemory::resource_ptr<VulkanVertexBuffer> vb,
fvkmemory::resource_ptr<VulkanIndexBuffer> ib);
~VulkanRenderPrimitive() = default;
fvkmemory::resource_ptr<VulkanVertexBuffer> vertexBuffer;
fvkmemory::resource_ptr<VulkanIndexBuffer> indexBuffer;
struct VulkanSamplerGroup : public HwSamplerGroup, VulkanResource {
// NOTE: we have to use out-of-line allocation here because the size of a Handle<> is limited
std::unique_ptr<SamplerGroup> sb;// FIXME: this shouldn't depend on filament::SamplerGroup
explicit VulkanSamplerGroup(size_t size) noexcept
: VulkanResource(VulkanResourceType::SAMPLER_GROUP),
sb(new SamplerGroup(size)) {}
};
struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource {
VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
~VulkanRenderPrimitive() {
mResources.clear();
}
VulkanVertexBuffer* vertexBuffer = nullptr;
VulkanIndexBuffer* indexBuffer = nullptr;
private:
// Keep references to the vertex buffer and the index buffer.
FixedSizeVulkanResourceManager<2> mResources;
};
struct VulkanFence : public HwFence, VulkanResource {
VulkanFence()
: VulkanResource(VulkanResourceType::FENCE) {}
explicit VulkanFence(std::shared_ptr<VulkanCmdFence> fence)
: VulkanResource(VulkanResourceType::FENCE),
fence(fence) {}
std::shared_ptr<VulkanCmdFence> fence;
};
struct VulkanTimerQuery : public HwTimerQuery, VulkanThreadSafeResource {
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
~VulkanTimerQuery();
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept;
bool isCompleted() noexcept;
uint32_t getStartingQueryIndex() const {
return mStartingQueryIndex;
}
uint32_t getStoppingQueryIndex() const {
return mStoppingQueryIndex;
}
private:
uint32_t mStartingQueryIndex;
uint32_t mStoppingQueryIndex;
std::shared_ptr<VulkanCmdFence> mFence;
utils::Mutex mFenceMutex;
};
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
BufferObjectBinding bindingType) noexcept {
switch(bindingType) {
@@ -76,6 +76,36 @@ 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:
@@ -31,7 +31,6 @@
#pragma clang diagnostic ignored "-Wnullability-completeness"
#pragma clang diagnostic ignored "-Wextra-semi"
#pragma clang diagnostic ignored "-Wc++98-compat-extra-semi"
#pragma clang diagnostic ignored "-Wthread-safety-analysis"
static const PFN_vkGetInstanceProcAddr& vkGetInstanceProcAddr = bluevk::vkGetInstanceProcAddr;
static const PFN_vkGetDeviceProcAddr& vkGetDeviceProcAddr = bluevk::vkGetDeviceProcAddr;
@@ -66,12 +66,17 @@ 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 {
@@ -241,7 +246,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
}
void VulkanPipelineCache::bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept {
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
mPipelineRequirements.shaders[0] = program->getVertexShader();
mPipelineRequirements.shaders[1] = program->getFragmentShader();
@@ -19,6 +19,7 @@
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include "VulkanResources.h"
#include "VulkanUtility.h"
#include <backend/DriverEnums.h>
@@ -43,6 +44,7 @@ namespace filament::backend {
struct VulkanProgram;
struct VulkanBufferObject;
struct VulkanTexture;
class VulkanResourceAllocator;
// VulkanPipelineCache manages a cache of descriptor sets and pipelines.
//
@@ -120,7 +122,7 @@ public:
void bindPipeline(VulkanCommandBuffer* commands);
// Each of the following methods are fast and do not make Vulkan calls.
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
void bindProgram(VulkanProgram* program) noexcept;
void bindRasterState(const RasterState& rasterState) noexcept;
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
@@ -117,10 +117,9 @@ void VulkanReadPixels::terminate() noexcept {
VulkanReadPixels::VulkanReadPixels(VkDevice device)
: mDevice(device) {}
void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget, uint32_t const x,
uint32_t const y, uint32_t const width, uint32_t const height,
uint32_t const graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
SelecteMemoryFunction const& selectMemoryFunc,
void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint32_t const y,
uint32_t const width, uint32_t const height, uint32_t const graphicsQueueFamilyIndex,
PixelBufferDescriptor&& pbd, SelecteMemoryFunction const& selectMemoryFunc,
OnReadCompleteFunction const& readCompleteFunc) {
assert_invariant(mDevice != VK_NULL_HANDLE);
@@ -144,7 +143,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
VkCommandPool& cmdpool = mCommandPool;
fvkmemory::resource_ptr<VulkanTexture> srcTexture = srcTarget->getColor0().texture;
VulkanTexture* srcTexture = srcTarget->getColor(0).texture;
assert_invariant(srcTexture);
VkFormat const srcFormat = srcTexture->getVkFormat();
bool const swizzle
@@ -231,9 +230,9 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
},
});
VulkanAttachment const srcAttachment = srcTarget->getColor0();
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange();
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::TRANSFER_SRC);
VkImageCopy const imageCopyRegion = {
.srcSubresource = {
@@ -269,7 +268,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
// Restore the source image layout.
srcTexture->transitionLayout(cmdbuffer, srcRange, srcTexture->getDefaultLayout());
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT);
vkEndCommandBuffer(cmdbuffer);
@@ -17,7 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
#define TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
#include "vulkan/memory/ResourcePointer.h"
#include "private/backend/Driver.h"
#include <bluevk/BlueVK.h>
@@ -73,9 +72,9 @@ public:
void terminate() noexcept;
void run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget, uint32_t x, uint32_t y,
uint32_t width, uint32_t height, uint32_t graphicsQueueFamilyIndex,
PixelBufferDescriptor&& pbd, SelecteMemoryFunction const& selectMemoryFunc,
void run(VulkanRenderTarget* srcTarget, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
uint32_t graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
SelecteMemoryFunction const& selectMemoryFunc,
OnReadCompleteFunction const& readCompleteFunc);
// This method will block until all of the in-flight requests are complete.
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
#define TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
#include "VulkanConstants.h"
#include "VulkanHandles.h"
#include <private/backend/HandleAllocator.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
#include <type_traits>
#include <unordered_set>
namespace filament::backend {
#define RESOURCE_TYPE_COUNT (static_cast<int>(VulkanResourceType::END_TYPE))
#define DEBUG_RESOURCE_LEAKS FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
#if DEBUG_RESOURCE_LEAKS
#define TRACK_INCREMENT() \
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]++; \
}
#define TRACK_DECREMENT() \
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]--; \
}
#else
// No-op
#define TRACK_INCREMENT()
#define TRACK_DECREMENT()
#endif
class VulkanResourceAllocator {
public:
using AllocatorImpl = HandleAllocatorVK;
VulkanResourceAllocator(size_t arenaSize, bool disableUseAfterFreeCheck)
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck)
#if DEBUG_RESOURCE_LEAKS
, mDebugOnlyResourceCount(RESOURCE_TYPE_COUNT) {
std::memset(mDebugOnlyResourceCount.data(), 0, sizeof(size_t) * RESOURCE_TYPE_COUNT);
}
#else
{}
#endif
template<typename D, typename... ARGS>
inline Handle<D> initHandle(ARGS&&... args) noexcept {
auto handle = mHandleAllocatorImpl.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
auto obj = handle_cast<D*>(handle);
obj->initResource(handle.getId());
TRACK_INCREMENT();
return handle;
}
template<typename D>
inline Handle<D> allocHandle() noexcept {
return mHandleAllocatorImpl.allocate<D>();
}
template<typename D, typename B, typename... ARGS>
inline typename std::enable_if<std::is_base_of<B, D>::value, D>::type* construct(
Handle<B> const& handle, ARGS&&... args) noexcept {
auto obj = mHandleAllocatorImpl.construct<D, B>(handle, std::forward<ARGS>(args)...);
obj->initResource(handle.getId());
TRACK_INCREMENT();
return obj;
}
template<typename Dp, typename B>
inline typename std::enable_if_t<
std::is_pointer_v<Dp> && std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B>& handle) noexcept {
return mHandleAllocatorImpl.handle_cast<Dp, B>(handle);
}
template<typename Dp, typename B>
inline typename std::enable_if_t<
std::is_pointer_v<Dp> && std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B> const& handle) noexcept {
return mHandleAllocatorImpl.handle_cast<Dp, B>(handle);
}
template<typename D, typename B>
inline void destruct(Handle<B> handle) noexcept {
auto obj = handle_cast<D*>(handle);
TRACK_DECREMENT();
mHandleAllocatorImpl.deallocate(handle, obj);
}
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
}
private:
AllocatorImpl mHandleAllocatorImpl;
#if DEBUG_RESOURCE_LEAKS
public:
void print() {
FVK_LOGD << "Resource Allocator state (debug only)" << utils::io::endl;
for (size_t i = 0; i < RESOURCE_TYPE_COUNT; i++) {
FVK_LOGD << "[" << i << "]=" << mDebugOnlyResourceCount[i] << utils::io::endl;
}
FVK_LOGD << "+++++++++++++++++++++++++++++++++++++" << utils::io::endl;
}
private:
utils::FixedCapacityVector<size_t> mDebugOnlyResourceCount;
#endif
};
#undef TRACK_INCREMENT
#undef TRACK_DECREMENT
#undef DEBUG_RESOURCE_LEAKS
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "VulkanResources.h"
#include "VulkanHandles.h"
#include "VulkanResourceAllocator.h"
#include "VulkanPipelineCache.h"
namespace filament::backend {
void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType type,
HandleBase::HandleId id) {
if (IS_HEAP_ALLOC_TYPE(type)) {
return;
}
switch (type) {
case VulkanResourceType::BUFFER_OBJECT:
allocator->destruct<VulkanBufferObject>(Handle<HwBufferObject>(id));
break;
case VulkanResourceType::INDEX_BUFFER:
allocator->destruct<VulkanIndexBuffer>(Handle<HwIndexBuffer>(id));
break;
case VulkanResourceType::PROGRAM:
allocator->destruct<VulkanProgram>(Handle<HwProgram>(id));
break;
case VulkanResourceType::RENDER_TARGET:
allocator->destruct<VulkanRenderTarget>(Handle<HwRenderTarget>(id));
break;
case VulkanResourceType::SAMPLER_GROUP:
allocator->destruct<VulkanSamplerGroup>(Handle<HwSamplerGroup>(id));
break;
case VulkanResourceType::SWAP_CHAIN:
allocator->destruct<VulkanSwapChain>(Handle<HwSwapChain>(id));
break;
case VulkanResourceType::TEXTURE:
allocator->destruct<VulkanTexture>(Handle<HwTexture>(id));
break;
case VulkanResourceType::TIMER_QUERY:
allocator->destruct<VulkanTimerQuery>(Handle<HwTimerQuery>(id));
break;
case VulkanResourceType::VERTEX_BUFFER_INFO:
allocator->destruct<VulkanVertexBufferInfo>(Handle<HwVertexBufferInfo>(id));
break;
case VulkanResourceType::VERTEX_BUFFER:
allocator->destruct<VulkanVertexBuffer>(Handle<HwVertexBuffer>(id));
break;
case VulkanResourceType::RENDER_PRIMITIVE:
allocator->destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;
case VulkanResourceType::DESCRIPTOR_SET_LAYOUT:
allocator->destruct<VulkanDescriptorSetLayout>(Handle<VulkanDescriptorSetLayout>(id));
break;
case VulkanResourceType::DESCRIPTOR_SET:
allocator->destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
break;
// If the resource is heap allocated, then the resource manager just skip refcounted
// destruction.
case VulkanResourceType::FENCE:
case VulkanResourceType::HEAP_ALLOCATED:
case VulkanResourceType::END_TYPE:
break;
}
}
} // namespace filament::backend
@@ -0,0 +1,313 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
#define TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
#include "VulkanUtility.h"
#include <backend/Handle.h>
#include <tsl/robin_set.h>
#include <utils/Mutex.h>
#include <utils/Panic.h>
#include <mutex>
#include <unordered_set>
namespace filament::backend {
class VulkanResourceAllocator;
struct VulkanThreadSafeResource;
// Subclasses of VulkanResource must provide this enum in their construction.
enum class VulkanResourceType : uint8_t {
BUFFER_OBJECT = 0,
INDEX_BUFFER = 1,
PROGRAM = 2,
RENDER_TARGET = 3,
SAMPLER_GROUP = 4,
SWAP_CHAIN = 5,
RENDER_PRIMITIVE = 6,
TEXTURE = 7,
TIMER_QUERY = 8,
VERTEX_BUFFER = 9,
VERTEX_BUFFER_INFO = 10,
DESCRIPTOR_SET_LAYOUT = 11,
DESCRIPTOR_SET = 12,
// Below are resources that are managed manually (i.e. not ref counted).
FENCE = 13,
HEAP_ALLOCATED = 14,
END_TYPE = 15, // A placeholder
};
#define IS_HEAP_ALLOC_TYPE(f) \
(f == VulkanResourceType::FENCE || f == VulkanResourceType::HEAP_ALLOCATED)
// This is a ref-counting base class that tracks how many references of this resource exist. This
// class is paired with VulkanResourceManagerImpl which is responsible for incrementing or
// decrementing the count. Once mRefCount == 0, VulkanResourceManagerImpl will also call the
// appropriate destructor. VulkanCommandBuffer, VulkanDriver, and composite structure like
// VulkanRenderPrimitive are owners of VulkanResourceManagerImpl instances.
struct VulkanResourceBase {
protected:
explicit VulkanResourceBase(VulkanResourceType type)
: mRefCount(IS_HEAP_ALLOC_TYPE(type) ? 1 : 0),
mType(uint32_t(type)),
mHandleId(0) {
}
private:
inline VulkanResourceType getType() const noexcept {
return VulkanResourceType(mType);
}
inline HandleBase::HandleId getId() const noexcept {
return mHandleId;
}
inline void initResource(HandleBase::HandleId id) noexcept {
mHandleId = id;
}
inline void ref() noexcept {
if (IS_HEAP_ALLOC_TYPE(getType())) {
return;
}
assert_invariant(mRefCount < ((1<<24) - 1));
++mRefCount;
}
inline void deref() noexcept {
if (IS_HEAP_ALLOC_TYPE(getType())) {
return;
}
assert_invariant(mRefCount > 0);
--mRefCount;
}
inline size_t refcount() const noexcept {
return mRefCount;
}
uint32_t mRefCount : 24; // 16M is enough for the refcount
uint32_t mType : 8; // must be uint32_t or MSVC doesn't pack it. no codegen impact w/ clang.
HandleBase::HandleId mHandleId;
friend struct VulkanThreadSafeResource;
friend class VulkanResourceAllocator;
template<typename RT, typename ST>
friend class VulkanResourceManagerImpl;
};
static_assert(sizeof(VulkanResourceBase) == 8, "VulkanResourceBase should be 8 bytes");
struct VulkanThreadSafeResource {
protected:
explicit VulkanThreadSafeResource(VulkanResourceType type)
: mImpl(type) {}
private:
inline VulkanResourceType getType() {
return mImpl.getType();
}
inline HandleBase::HandleId getId() {
return mImpl.getId();
}
inline void initResource(HandleBase::HandleId id) noexcept {
std::unique_lock<utils::Mutex> lock(mMutex);
mImpl.initResource(id);
}
inline void ref() noexcept {
std::unique_lock<utils::Mutex> lock(mMutex);
mImpl.ref();
}
inline void deref() noexcept {
std::unique_lock<utils::Mutex> lock(mMutex);
mImpl.deref();
}
inline size_t refcount() noexcept {
std::unique_lock<utils::Mutex> lock(mMutex);
return mImpl.refcount();
}
utils::Mutex mMutex;
VulkanResourceBase mImpl;
friend class VulkanResourceAllocator;
template<typename RT, typename ST>
friend class VulkanResourceManagerImpl;
};
using VulkanResource = VulkanResourceBase;
namespace {
// When the size of the resource set is known to be small, (for example for VulkanRenderPrimitive),
// we just use a std::array to back the set.
template<std::size_t SIZE>
using FixedCapacityResourceSet = CappedArray<VulkanResource*, SIZE>;
// robin_set/map are useful for sets that are acquire only and the set will be iterated when the set
// is cleared.
using FastIterationResourceSet = tsl::robin_set<VulkanResource*>;
// unoredered_set is used in the general case where insert/erase can occur at will. This is useful
// for the basic object ownership count - i.e. VulkanDriver.
using ResourceSet = std::unordered_set<VulkanResource*>;
using ThreadSafeResourceSet = std::unordered_set<VulkanThreadSafeResource*>;
} // anonymous namespace
class VulkanResourceAllocator;
#define LOCK_IF_NEEDED() \
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) { \
mMutex->lock(); \
}
#define UNLOCK_IF_NEEDED() \
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) { \
mMutex->unlock(); \
}
void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType type,
HandleBase::HandleId id);
template<typename ResourceType, typename SetType>
class VulkanResourceManagerImpl {
public:
explicit VulkanResourceManagerImpl(VulkanResourceAllocator* allocator)
: mAllocator(allocator) {
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) {
mMutex = std::make_unique<utils::Mutex>();
}
}
VulkanResourceManagerImpl(const VulkanResourceManagerImpl& other) = delete;
void operator=(const VulkanResourceManagerImpl& other) = delete;
VulkanResourceManagerImpl(const VulkanResourceManagerImpl&& other) = delete;
void operator=(const VulkanResourceManagerImpl&& other) = delete;
~VulkanResourceManagerImpl() {
clear();
}
inline void acquire(ResourceType* resource) {
if (IS_HEAP_ALLOC_TYPE(resource->getType())) {
return;
}
LOCK_IF_NEEDED();
if (mResources.find(resource) != mResources.end()) {
UNLOCK_IF_NEEDED();
return;
}
mResources.insert(resource);
UNLOCK_IF_NEEDED();
resource->ref();
}
// Transfers ownership from one resource set to another
template <typename tSetType>
inline void acquireAll(VulkanResourceManagerImpl<ResourceType, tSetType>* srcResources) {
copyAll(srcResources);
srcResources->clear();
}
// Transfers ownership from one resource set to another
template <typename tSetType>
inline void copyAll(VulkanResourceManagerImpl<ResourceType, tSetType>* srcResources) {
LOCK_IF_NEEDED();
for (auto iter = srcResources->mResources.begin(); iter != srcResources->mResources.end();
iter++) {
acquire(*iter);
}
UNLOCK_IF_NEEDED();
}
inline void release(ResourceType* resource) {
if (IS_HEAP_ALLOC_TYPE(resource->getType())) {
return;
}
LOCK_IF_NEEDED();
auto resItr = mResources.find(resource);
if (resItr == mResources.end()) {
UNLOCK_IF_NEEDED();
return;
}
mResources.erase(resItr);
UNLOCK_IF_NEEDED();
derefImpl(resource);
}
inline void clear() {
LOCK_IF_NEEDED();
for (auto iter = mResources.begin(); iter != mResources.end(); iter++) {
derefImpl(*iter);
}
mResources.clear();
UNLOCK_IF_NEEDED();
}
inline size_t size() {
return mResources.size();
}
private:
inline void derefImpl(ResourceType* resource) {
resource->deref();
if (resource->refcount() != 0) {
return;
}
deallocateResource(mAllocator, resource->getType(), resource->getId());
}
VulkanResourceAllocator* mAllocator;
SetType mResources;
std::unique_ptr<utils::Mutex> mMutex;
template <typename, typename> friend class VulkanResourceManagerImpl;
};
using VulkanAcquireOnlyResourceManager
= VulkanResourceManagerImpl<VulkanResource, FastIterationResourceSet>;
using VulkanResourceManager = VulkanResourceManagerImpl<VulkanResource, ResourceSet>;
template<std::size_t SIZE>
using FixedSizeVulkanResourceManager =
VulkanResourceManagerImpl<VulkanResource, FixedCapacityResourceSet<SIZE>>;
using VulkanThreadSafeResourceManager
= VulkanResourceManagerImpl<VulkanThreadSafeResource, ThreadSafeResourceSet>;
#undef LOCK_IF_NEEDED
#undef UNLOCK_IF_NEEDED
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
@@ -16,7 +16,6 @@
#include "VulkanStagePool.h"
#include "VulkanCommands.h"
#include "VulkanConstants.h"
#include "VulkanImageUtility.h"
#include "VulkanMemory.h"
@@ -38,7 +37,6 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
if (iter != mFreeStages.end()) {
auto stage = iter->second;
mFreeStages.erase(iter);
stage->lastAccessed = mCurrentFrame;
mUsedStages.insert(stage);
return stage;
}
@@ -61,7 +59,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo,
&allocInfo, &stage->buffer, &stage->memory, nullptr);
#if FVK_ENABLED(FVK_DEBUG_STAGING_ALLOCATION)
#if FVK_ENABLED(FVK_DEBUG_ALLOCATION)
if (result != VK_SUCCESS) {
FVK_LOGE << "Allocation error: " << result << utils::io::endl;
}
@@ -76,7 +74,6 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
for (auto image : mFreeImages) {
if (image->format == vkformat && image->width == width && image->height == height) {
mFreeImages.erase(image);
image->lastAccessed = mCurrentFrame;
mUsedImages.insert(image);
return image;
}
@@ -17,7 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#define TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#include "backend/DriverEnums.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include <map>
@@ -25,8 +26,6 @@
namespace filament::backend {
class VulkanCommands;
// Immutable POD representing a shared CPU-GPU staging area.
struct VulkanStage {
VmaAllocation memory;
+15 -28
View File
@@ -15,8 +15,6 @@
*/
#include "VulkanSwapChain.h"
#include "VulkanCommands.h"
#include "VulkanTexture.h"
#include <utils/FixedCapacityVector.h>
@@ -28,18 +26,19 @@ using namespace utils;
namespace filament::backend {
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
fvkmemory::ResourceManager* resourceManager, VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags,
VkExtent2D extent)
: mPlatform(platform),
mResourceManager(resourceManager),
VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent)
: VulkanResource(VulkanResourceType::SWAP_CHAIN),
mPlatform(platform),
mCommands(commands),
mAllocator(allocator),
mHandleAllocator(handleAllocator),
mStagePool(stagePool),
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
mTransitionSwapChainImageLayoutForPresent(
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
mAcquired(false),
mIsFirstRenderPass(true) {
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
@@ -54,9 +53,6 @@ VulkanSwapChain::~VulkanSwapChain() {
mCommands->flush();
mCommands->wait();
mColors = {};
mDepth = {};
mPlatform->destroy(swapChain);
}
@@ -67,23 +63,14 @@ void VulkanSwapChain::update() {
mColors.reserve(bundle.colors.size());
VkDevice const device = mPlatform->getDevice();
TextureUsage depthUsage = TextureUsage::DEPTH_ATTACHMENT;
TextureUsage colorUsage = TextureUsage::COLOR_ATTACHMENT;
if (bundle.isProtected) {
depthUsage |= TextureUsage::PROTECTED;
colorUsage |= TextureUsage::PROTECTED;
}
for (auto const color: bundle.colors) {
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
device, mAllocator, mResourceManager, mCommands, color, bundle.colorFormat, 1,
bundle.extent.width, bundle.extent.height, TextureUsage::COLOR_ATTACHMENT,
mStagePool);
mColors.push_back(colorTexture);
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 */));
}
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, device, mAllocator,
mResourceManager, mCommands, bundle.depth, bundle.depthFormat, 1, bundle.extent.width,
bundle.extent.height, TextureUsage::DEPTH_ATTACHMENT, mStagePool);
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 */);
mExtent = bundle.extent;
}
@@ -102,7 +89,7 @@ void VulkanSwapChain::present() {
}
mCommands->flush();
// call the image ready wait function
if (mExplicitImageReadyWait != nullptr) {
mExplicitImageReadyWait(swapChain);
@@ -142,7 +129,7 @@ void VulkanSwapChain::acquire(bool& resized) {
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
mCurrentSwapIndex = imageSyncData.imageIndex;
mExplicitImageReadyWait = imageSyncData.explicitImageReadyWait;
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain.";
if (imageSyncData.imageReadySemaphore != VK_NULL_HANDLE) {
mCommands->injectDependency(imageSyncData.imageReadySemaphore);
+15 -18
View File
@@ -19,9 +19,9 @@
#include "DriverBase.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanTexture.h"
#include "vulkan/memory/Resource.h"
#include "VulkanResources.h"
#include <backend/platforms/VulkanPlatform.h>
@@ -36,14 +36,14 @@ namespace filament::backend {
struct VulkanHeadlessSwapChain;
struct VulkanSurfaceSwapChain;
class VulkanCommands;
class VulkanResourceAllocator;
// A wrapper around the platform implementation of swapchain.
struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
fvkmemory::ResourceManager* resourceManager, VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow,
uint64_t flags, VkExtent2D extent = {0, 0});
struct VulkanSwapChain : public HwSwapChain, VulkanResource {
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands, VulkanResourceAllocator* handleAllocator,
VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
~VulkanSwapChain();
@@ -51,15 +51,15 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
void acquire(bool& reized);
fvkmemory::resource_ptr<VulkanTexture> getCurrentColor() const noexcept {
inline VulkanTexture* getCurrentColor() const noexcept {
uint32_t const imageIndex = mCurrentSwapIndex;
FILAMENT_CHECK_PRECONDITION(
imageIndex != VulkanPlatform::ImageSyncData::INVALID_IMAGE_INDEX);
return mColors[imageIndex];
return mColors[imageIndex].get();
}
inline fvkmemory::resource_ptr<VulkanTexture> getDepth() const noexcept {
return mDepth;
inline VulkanTexture* getDepth() const noexcept {
return mDepth.get();
}
inline bool isFirstRenderPass() const noexcept {
@@ -74,18 +74,15 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
return mExtent;
}
inline bool isProtected() noexcept {
return mPlatform->isProtected(swapChain);
}
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
void update();
VulkanPlatform* mPlatform;
fvkmemory::ResourceManager* mResourceManager;
VulkanCommands* mCommands;
VmaAllocator mAllocator;
VulkanResourceAllocator* const mHandleAllocator;
VulkanStagePool& mStagePool;
bool const mHeadless;
bool const mFlushAndWaitOnResize;
@@ -93,8 +90,8 @@ private:
// We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout
// transitions, which are useful here.
utils::FixedCapacityVector<fvkmemory::resource_ptr<VulkanTexture>> mColors;
fvkmemory::resource_ptr<VulkanTexture> mDepth;
utils::FixedCapacityVector<std::unique_ptr<VulkanTexture>> mColors;
std::unique_ptr<VulkanTexture> mDepth;
VkExtent2D mExtent;
uint32_t mCurrentSwapIndex;
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
+165 -223
View File
@@ -14,11 +14,10 @@
* limitations under the License.
*/
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include "VulkanResourceAllocator.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
#include "vulkan/memory/ResourcePointer.h"
#include <DataReshaper.h>
#include <backend/DriverEnums.h>
@@ -89,114 +88,84 @@ 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, prevExplicit, 0),
compose(next.g, prevExplicit, 1),
compose(next.b, prevExplicit, 2),
compose(next.a, prevExplicit, 3),
compose(next.r, prev, 0),
compose(next.g, prev, 1),
compose(next.b, prev, 2),
compose(next.a, prev, 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, VulkanLayout defaultLayout,
bool isProtected)
: mVkFormat(format),
mViewType(viewType),
mFullViewRange{filament::backend::getImageAspect(format), 0, levels, 0, layerCount},
mDefaultLayout(defaultLayout),
mIsProtected(isProtected),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands),
mIsTransientAttachment(false) {}
VulkanTextureState::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) {
}
// Constructor for internally passed VkImage
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool)
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
tusage),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
allocator, commands, stagePool, format, imgutil::getViewType(SamplerType::SAMPLER_2D),
1, 1, getDefaultLayoutImpl(tusage), any(usage & TextureUsage::PROTECTED))) {
mState->mTextureImage = image;
mPrimaryViewRange = mState->mFullViewRange;
VulkanTextureState* VulkanTexture::getSharedState() {
VulkanTextureState* state = mAllocator->handle_cast<VulkanTextureState*>(mState);
return state;
}
VulkanTextureState const* VulkanTexture::getSharedState() const {
VulkanTextureState const* state = mAllocator->handle_cast<VulkanTextureState const*>(mState);
return state;
}
VulkanTexture::VulkanTexture(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(
device, allocator, commands, stagePool,
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1)) {
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,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
allocator, commands, stagePool, backend::getVkFormat(tformat),
imgutil::getViewType(target), levels, getLayerCount(target, depth),
VulkanLayout::UNDEFINED, any(usage & TextureUsage::PROTECTED))) {
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
getLayerCount(target, depth))) {
auto* const state = getSharedState();
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
VkImageCreateInfo imageInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
.format = mState->mVkFormat,
.extent = {w, h, depth},
.mipLevels = levels,
.arrayLayers = 1,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = 0,
};
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
.format = state->mVkFormat,
.extent = {w, h, depth},
.mipLevels = levels,
.arrayLayers = 1,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = 0};
if (target == SamplerType::SAMPLER_CUBEMAP) {
imageInfo.arrayLayers = 6;
imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
@@ -216,9 +185,6 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.arrayLayers = depth * 6;
imageInfo.extent.depth = 1;
}
if (any(usage & TextureUsage::PROTECTED)) {
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
}
if (any(usage & TextureUsage::BLIT_SRC)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
@@ -227,19 +193,6 @@ 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);
mState->mIsTransientAttachment = useTransientAttachment;
const VkImageUsageFlags transientFlag =
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
if (any(usage & TextureUsage::SAMPLEABLE)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
@@ -255,19 +208,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 | transientFlag;
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
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 | transientFlag;
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
}
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 | transientFlag;
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// Depth resolves uses a custom shader and therefore needs to be sampleable.
if (samples > 1) {
@@ -280,7 +233,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// any kind of attachment (color or depth).
const auto& limits = context.getPhysicalDeviceLimits();
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
samples = reduceSampleCount(samples, isVkDepthFormat(mState->mVkFormat)
samples = reduceSampleCount(samples, isVkDepthFormat(state->mVkFormat)
? limits.sampledImageDepthSampleCounts
: limits.sampledImageColorSampleCounts);
}
@@ -294,12 +247,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
this->samples = samples;
imageInfo.samples = (VkSampleCountFlagBits) samples;
VkResult error = vkCreateImage(mState->mDevice, &imageInfo, VKALLOC, &mState->mTextureImage);
VkResult error = vkCreateImage(state->mDevice, &imageInfo, VKALLOC, &state->mTextureImage);
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
FVK_LOGD << "vkCreateImage: "
<< "image = " << mState->mTextureImage << ", "
<< "image = " << state->mTextureImage << ", "
<< "result = " << error << ", "
<< "handle = " << utils::io::hex << mState->mTextureImage << utils::io::dec << ", "
<< "handle = " << utils::io::hex << state->mTextureImage << utils::io::dec << ", "
<< "extent = " << w << "x" << h << "x"<< depth << ", "
<< "mipLevels = " << int(levels) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
@@ -308,20 +261,16 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "target = " << static_cast<int>(target) <<", "
<< "format = " << mState->mVkFormat << utils::io::endl;
<< "format = " << state->mVkFormat << utils::io::endl;
}
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image.";
// Allocate memory for the VkImage and bind it.
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(mState->mDevice, mState->mTextureImage, &memReqs);
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
const VkFlags requiredMemoryFlags =
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
(mState->mIsProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
uint32_t memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
<< "VulkanTexture: unable to find a memory type that meets requirements.";
@@ -331,52 +280,67 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
error = vkAllocateMemory(mState->mDevice, &allocInfo, nullptr, &mState->mTextureImageMemory);
error = vkAllocateMemory(state->mDevice, &allocInfo, nullptr, &state->mTextureImageMemory);
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory.";
error = vkBindImageMemory(mState->mDevice, mState->mTextureImage, mState->mTextureImageMemory,
0);
error = vkBindImageMemory(state->mDevice, state->mTextureImage, state->mTextureImageMemory, 0);
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to bind image.";
// Spec out the "primary" VkImageView that shaders use to sample from the image.
mPrimaryViewRange = mState->mFullViewRange;
mPrimaryViewRange = state->mFullViewRange;
// Go ahead and create the primary image view.
getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
mState->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
commandsBuf.acquire(this);
transitionLayout(&commandsBuf, mPrimaryViewRange, imgutil::getDefaultLayout(imageInfo.usage));
}
// Constructor for creating a texture view
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage) {
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();
state->refs++;
mPrimaryViewRange = src->mPrimaryViewRange;
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
mPrimaryViewRange.levelCount = levelCount;
}
// Constructor for creating a texture view with swizzle
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage) {
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++;
mPrimaryViewRange = src->mPrimaryViewRange;
mSwizzle = composeSwizzle(src->mSwizzle, swizzle);
}
VulkanTextureState::~VulkanTextureState() {
if (mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
}
for (auto entry: mCachedImageViews) {
vkDestroyImageView(mDevice, entry.second, VKALLOC);
VulkanTexture::~VulkanTexture() {
auto* const state = getSharedState();
state->refs--;
if (state->refs == 0) {
if (state->mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(state->mDevice, state->mTextureImage, VKALLOC);
vkFreeMemory(state->mDevice, state->mTextureImageMemory, VKALLOC);
}
for (auto entry: state->mCachedImageViews) {
vkDestroyImageView(state->mDevice, entry.second, VKALLOC);
}
mAllocator->destruct<VulkanTextureState>(mState);
}
}
@@ -385,7 +349,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
assert_invariant(width <= this->width && height <= this->height);
assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP ||
target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1));
assert_invariant(!mState->mIsProtected);
auto* const state = getSharedState();
const PixelBufferDescriptor* hostData = &data;
PixelBufferDescriptor reshapedData;
@@ -398,7 +362,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
// If format conversion is both required and supported, use vkCmdBlitImage.
const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type);
const VkFormat deviceFormat = getVkFormatLinear(mState->mVkFormat);
const VkFormat deviceFormat = getVkFormatLinear(state->mVkFormat);
if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) {
assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 &&
"Offsets not yet supported when format conversion is required.");
@@ -410,16 +374,16 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
// Otherwise, use vkCmdCopyBufferToImage.
void* mapped = nullptr;
VulkanStage const* stage = mState->mStagePool.acquireStage(hostData->size);
VulkanStage const* stage = state->mStagePool.acquireStage(hostData->size);
assert_invariant(stage->memory);
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData->buffer, hostData->size);
vmaUnmapMemory(mState->mAllocator, stage->memory);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, hostData->size);
vmaUnmapMemory(state->mAllocator, stage->memory);
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size);
VulkanCommandBuffer& commands = mState->mCommands->get();
VulkanCommandBuffer& commands = state->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
commands.acquire(this);
VkBufferImageCopy copyRegion = {
.bufferOffset = {},
@@ -460,29 +424,30 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
VkImageLayout const newVkLayout = imgutil::getVkLayout(newLayout);
if (nextLayout == VulkanLayout::UNDEFINED) {
nextLayout = getDefaultLayout();
nextLayout = imgutil::getDefaultLayout(this->usage);
}
transitionLayout(&commands, transitionRange, newLayout);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mState->mTextureImage, newVkLayout, 1, &copyRegion);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, &copyRegion);
transitionLayout(&commands, transitionRange, nextLayout);
}
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, uint32_t miplevel) {
auto* const state = getSharedState();
void* mapped = nullptr;
VulkanStageImage const* stage
= mState->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
= state->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
vmaMapMemory(state->mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData.buffer, hostData.size);
vmaUnmapMemory(mState->mAllocator, stage->memory);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, hostData.size);
vmaUnmapMemory(state->mAllocator, stage->memory);
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData.size);
VulkanCommandBuffer& commands = mState->mCommands->get();
VulkanCommandBuffer& commands = state->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
commands.acquire(this);
// TODO: support blit-based format conversion for 3D images and cubemaps.
const int layer = 0;
@@ -505,15 +470,11 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
transitionLayout(&commands, range, newLayout);
vkCmdBlitImage(cmdbuf, stage->image, imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
mState->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
state->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
transitionLayout(&commands, range, oldLayout);
}
VulkanLayout VulkanTexture::getDefaultLayout() const {
return mState->mDefaultLayout;
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
range.levelCount = 1;
range.layerCount = 1;
@@ -530,43 +491,44 @@ VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range,
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle) {
auto* const state = getSharedState();
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
auto iter = mState->mCachedImageViews.find(key);
if (iter != mState->mCachedImageViews.end()) {
auto iter = state->mCachedImageViews.find(key);
if (iter != state->mCachedImageViews.end()) {
return iter->second;
}
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = mState->mTextureImage,
.image = state->mTextureImage,
.viewType = viewType,
.format = mState->mVkFormat,
.format = state->mVkFormat,
.components = swizzle,
.subresourceRange = range,
};
VkImageView imageView;
vkCreateImageView(mState->mDevice, &viewInfo, VKALLOC, &imageView);
mState->mCachedImageViews.emplace(key, imageView);
vkCreateImageView(state->mDevice, &viewInfo, VKALLOC, &imageView);
state->mCachedImageViews.emplace(key, imageView);
return imageView;
}
VkImageAspectFlags VulkanTexture::getImageAspect() const {
// Helper function in VulkanUtility
return filament::backend::getImageAspect(mState->mVkFormat);
auto* const state = getSharedState();
return filament::backend::getImageAspect(state->mVkFormat);
}
bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range, VulkanLayout newLayout) {
if (transitionLayout(commands->buffer(), range, newLayout)) {
commands->acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
return true;
}
return false;
const VkImageSubresourceRange& range, VulkanLayout newLayout) {
return transitionLayout(commands->buffer(), commands->fence, range, newLayout);
}
bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range,
bool VulkanTexture::transitionLayout(
VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
const VkImageSubresourceRange& range,
VulkanLayout newLayout) {
auto* const state = getSharedState();
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
uint32_t const firstLayer = range.baseArrayLayer;
@@ -597,7 +559,7 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
continue;
}
hasTransitions = hasTransitions || imgutil::transitionLayout(cmdbuf, {
.image = mState->mTextureImage,
.image = state->mTextureImage,
.oldLayout = layout,
.newLayout = newLayout,
.subresources = {
@@ -612,18 +574,17 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
}
} else if (newLayout != oldLayout) {
hasTransitions = imgutil::transitionLayout(cmdbuf, {
.image = mState->mTextureImage,
.image = state->mTextureImage,
.oldLayout = oldLayout,
.newLayout = newLayout,
.subresources = range,
});
}
// 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) {
state->mTransitionFence = fence;
setLayout(range, newLayout);
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
@@ -642,32 +603,10 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
return hasTransitions;
}
void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range) {
VkCommandBuffer const cmdbuf = commands->buffer();
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 = mState->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();
auto* const state = getSharedState();
VkImageLayout const layout
= imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
VkImageMemoryBarrier barrier = {
@@ -678,7 +617,7 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = mState->mTextureImage,
.image = state->mTextureImage,
.subresourceRange = range,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
@@ -686,6 +625,7 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
}
void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout) {
auto* const state = getSharedState();
uint32_t const firstLayer = range.baseArrayLayer;
uint32_t const lastLayer = firstLayer + range.layerCount;
uint32_t const firstLevel = range.baseMipLevel;
@@ -698,32 +638,34 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
uint32_t const first = (layer << 16) | firstLevel;
uint32_t const last = (layer << 16) | lastLevel;
mState->mSubresourceLayouts.clear(first, last);
state->mSubresourceLayouts.clear(first, last);
}
} else {
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
uint32_t const first = (layer << 16) | firstLevel;
uint32_t const last = (layer << 16) | lastLevel;
mState->mSubresourceLayouts.add(first, last, newLayout);
state->mSubresourceLayouts.add(first, last, newLayout);
}
}
}
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
assert_invariant(level <= 0xffff && layer <= 0xffff);
auto* const state = getSharedState();
const uint32_t key = (layer << 16) | level;
if (!mState->mSubresourceLayouts.has(key)) {
if (!state->mSubresourceLayouts.has(key)) {
return VulkanLayout::UNDEFINED;
}
return mState->mSubresourceLayouts.get(key);
return state->mSubresourceLayouts.get(key);
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void VulkanTexture::print() const {
auto* const state = getSharedState();
uint32_t const firstLayer = 0;
uint32_t const lastLayer = firstLayer + mState->mFullViewRange.layerCount;
uint32_t const lastLayer = firstLayer + mFullViewRange.layerCount;
uint32_t const firstLevel = 0;
uint32_t const lastLevel = firstLevel + mState->mFullViewRange.levelCount;
uint32_t const lastLevel = firstLevel + mFullViewRange.levelCount;
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
for (uint32_t level = firstLevel; level < lastLevel; ++level) {
@@ -732,16 +674,16 @@ void VulkanTexture::print() const {
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
level >= mPrimaryViewRange.baseMipLevel &&
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
FVK_LOGD << "[" << mState->mTextureImage << "]: (" << layer << "," << level
FVK_LOGD << "[" << state->mTextureImage << "]: (" << layer << "," << level
<< ")=" << getLayout(layer, level)
<< " primary=" << primary
<< utils::io::endl;
}
}
for (auto view: mState->mCachedImageViews) {
for (auto view: state->mCachedImageViews) {
auto& range = view.first.range;
FVK_LOGD << "[" << mState->mTextureImage << ", imageView=" << view.second << "]=>"
FVK_LOGD << "[" << state->mTextureImage << ", imageView=" << view.second << "]=>"
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type
+58 -51
View File
@@ -20,9 +20,8 @@
#include "DriverBase.h"
#include "VulkanBuffer.h"
#include "VulkanResources.h"
#include "VulkanImageUtility.h"
#include "vulkan/memory/Resource.h"
#include "vulkan/memory/ResourcePointer.h"
#include <utils/Hash.h>
#include <utils/RangeMap.h>
@@ -31,12 +30,12 @@
namespace filament::backend {
struct VulkanTextureState : public fvkmemory::Resource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
uint8_t layerCount, VulkanLayout defaultLayout, bool isProtected);
class VulkanResourceAllocator;
~VulkanTextureState();
struct VulkanTextureState : public VulkanResource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount);
struct ImageViewKey {
VkImageSubresourceRange range; // 4 * 5 bytes
@@ -59,17 +58,17 @@ struct VulkanTextureState : public fvkmemory::Resource {
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
uint32_t refs = 1;
// The texture with the sidecar owns the sidecar.
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
std::unique_ptr<VulkanTexture> mSidecarMSAA;
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
VkFormat const mVkFormat;
VkImageViewType const mViewType;
VkImageSubresourceRange const mFullViewRange;
VkImage mTextureImage = VK_NULL_HANDLE;
VulkanLayout mDefaultLayout;
bool mIsProtected = false;
VkImage mTextureImage = VK_NULL_HANDLE;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
@@ -79,35 +78,40 @@ struct VulkanTextureState : public fvkmemory::Resource {
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
bool mIsTransientAttachment;
std::shared_ptr<VulkanCmdFence> mTransitionFence;
};
struct VulkanTexture : public HwTexture, fvkmemory::Resource {
struct VulkanTexture : public HwTexture, VulkanResource {
// Standard constructor for user-facing textures.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, fvkmemory::ResourceManager* resourceManager,
VulkanCommands* commands, SamplerType target, uint8_t levels, TextureFormat tformat,
uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage tusage,
VulkanStagePool& stagePool);
VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false);
// Specialized constructor for internally created textures (e.g. from a swap chain)
// The texture will never destroy the given VkImage, but it does manages its subresources.
VulkanTexture(VkDevice device, VmaAllocator allocator,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VkImage image,
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool);
VulkanStagePool& stagePool, bool heapAllocated = false);
// Constructor for creating a texture view for wrt specific mip range
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel, uint8_t levelCount);
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount);
// Constructor for creating a texture view for swizzle.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands,
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle);
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle);
~VulkanTexture() = default;
~VulkanTexture();
// Uploads data into a subregion of a 2D or 3D texture.
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
@@ -115,23 +119,21 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
// Returns the primary image view, which is used for shader sampling.
VkImageView getPrimaryImageView() {
return getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
VulkanTextureState* state = getSharedState();
return getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
}
VkImageViewType getViewType() const {
return mState->mViewType;
VulkanTextureState const* state = getSharedState();
return state->mViewType;
}
VkImageSubresourceRange const& getPrimaryViewRange() const { return mPrimaryViewRange; }
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
VulkanLayout getPrimaryImageLayout() const {
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.
@@ -148,42 +150,35 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
VkFormat getVkFormat() const {
return mState->mVkFormat;
VulkanTextureState const* state = getSharedState();
return state->mVkFormat;
}
VkImage getVkImage() const {
return mState->mTextureImage;
VulkanTextureState const* state = getSharedState();
return state->mTextureImage;
}
VulkanLayout getLayout(uint32_t layer, uint32_t level) const;
void setSidecar(fvkmemory::resource_ptr<VulkanTexture> sidecar) {
mState->mSidecarMSAA = sidecar;
void setSidecar(VulkanTexture* sidecar) {
VulkanTextureState* state = getSharedState();
state->mSidecarMSAA.reset(sidecar);
}
fvkmemory::resource_ptr<VulkanTexture> getSidecar() const {
return mState->mSidecarMSAA;
VulkanTexture* getSidecar() const {
VulkanTextureState const* state = getSharedState();
return state->mSidecarMSAA.get();
}
bool isTransientAttachment() const {
return mState->mIsTransientAttachment;
}
bool getIsProtected() const {
return mState->mIsProtected;
}
bool transitionLayout(VulkanCommandBuffer* commands, VkImageSubresourceRange const& range,
bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range,
VulkanLayout newLayout);
bool transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range,
VulkanLayout newLayout);
bool transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr<VulkanCmdFence> fence,
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;
@@ -192,11 +187,21 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
// 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
private:
VulkanTextureState* getSharedState();
VulkanTextureState const* getSharedState() const;
// Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and
// swizzle (or not).
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
@@ -205,7 +210,9 @@ private:
void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height,
uint32_t depth, uint32_t miplevel);
fvkmemory::resource_ptr<VulkanTextureState> mState;
VulkanResourceAllocator* const mAllocator;
Handle<VulkanTextureState> mState;
// Track the range of subresources that define the "primary" image view, which is the special
// image view that gets bound to an actual texture sampler.
@@ -16,17 +16,16 @@
#include "VulkanDescriptorSetManager.h"
#include <vulkan/VulkanCommands.h>
#include <vulkan/VulkanHandles.h>
#include <vulkan/VulkanUtility.h>
#include <vulkan/VulkanConstants.h>
#include <vulkan/VulkanImageUtility.h>
#include <vulkan/VulkanResources.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
#include <math.h>
#include <algorithm>
#include <memory>
#include <type_traits>
#include <vector>
@@ -122,9 +121,8 @@ public:
return count == mCount;
}
VkDescriptorSet obtainSet(VkDescriptorSetLayout vklayout) {
auto itr = findSets(vklayout);
if (itr != mUnused.end()) {
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
if (UnusedSetMap::iterator itr = mUnused.find(layout->bitmask); itr != mUnused.end()) {
// If we don't have any unused, then just return an empty handle.
if (itr->second.empty()) {
return VK_NULL_HANDLE;
@@ -139,7 +137,7 @@ public:
return VK_NULL_HANDLE;
}
// Creating a new set
VkDescriptorSetLayout layouts[1] = {vklayout};
VkDescriptorSetLayout layouts[1] = {layout->vklayout};
VkDescriptorSetAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.pNext = nullptr,
@@ -156,29 +154,19 @@ public:
return vkSet;
}
void recycle(VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
void recycle(BitmaskGroup const& layoutMask, 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.
auto itr = findSets(vklayout);
if (itr != mUnused.end()) {
if (auto itr = mUnused.find(layoutMask); itr != mUnused.end()) {
itr->second.push_back(vkSet);
} else {
mUnused.push_back(std::make_pair(vklayout, std::vector<VkDescriptorSet> {vkSet}));
mUnused[layoutMask].push_back(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;
@@ -189,83 +177,16 @@ private:
// Tracks the number of in-use descriptor sets.
uint16_t mUnusedCount;
// This maps a layout to a list of descriptor sets allocated for that layout.
// 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>;
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 VulkanDescriptorSetManager::DescriptorInfinitePool {
class DescriptorInfinitePool {
private:
static constexpr uint16_t EXPECTED_SET_COUNT = 10;
static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5;
@@ -274,14 +195,13 @@ public:
DescriptorInfinitePool(VkDevice device)
: mDevice(device) {}
VkDescriptorSet obtainSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
auto const vklayout = layout->getVkLayout();
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
DescriptorPool* sameTypePool = nullptr;
for (auto& pool: mPools) {
if (!pool->canAllocate(layout->count)) {
continue;
}
if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) {
if (auto set = pool->obtainSet(layout); set != VK_NULL_HANDLE) {
return set;
}
if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) {
@@ -299,18 +219,18 @@ public:
mPools.push_back(std::make_unique<DescriptorPool>(mDevice,
DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity));
auto& pool = mPools.back();
auto ret = pool->obtainSet(vklayout);
auto ret = pool->obtainSet(layout);
assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?");
return ret;
}
void recycle(DescriptorCount const& count, VkDescriptorSetLayout vklayout,
void recycle(DescriptorCount const& count, BitmaskGroup const& layoutMask,
VkDescriptorSet vkSet) {
for (auto& pool: mPools) {
if (!pool->canAllocate(count)) {
continue;
}
pool->recycle(vklayout, vkSet);
pool->recycle(layoutMask, vkSet);
break;
}
}
@@ -320,187 +240,315 @@ private:
std::vector<std::unique_ptr<DescriptorPool>> mPools;
};
class VulkanDescriptorSetManager::DescriptorSetLayoutManager {
public:
DescriptorSetLayoutManager(VkDevice device)
: mDevice(device) {}
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));
}
};
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks) {
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
return itr->second;
} // 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();
}
auto vklayout = createLayout(mDevice, bitmasks);
mVkLayouts[bitmasks] = vklayout;
return vklayout;
~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;
}
};
public:
Impl(VkDevice device, VulkanResourceAllocator* resourceAllocator)
: mDevice(device),
mResourceAllocator(resourceAllocator),
mDescriptorPool(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();
}
mStashedSets[setIndex] = &history;
}
~DescriptorSetLayoutManager() {
for (auto& itr: mVkLayouts) {
vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC);
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;
}
}
}
private:
VkDevice mDevice;
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
mVkLayouts;
VulkanResourceAllocator* mResourceAllocator;
DescriptorInfinitePool mDescriptorPool;
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
std::unordered_map<VkDescriptorSet, DescriptorSetHistory> mHistory;
DescriptorSetHistoryArray mStashedSets;
BoundInfo mLastBoundInfo;
VkDescriptorBufferInfo mPlaceHolderBufferInfo;
VkDescriptorImageInfo mPlaceHolderImageInfo;
};
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
fvkmemory::ResourceManager* resourceManager)
: mDevice(device),
mResourceManager(resourceManager),
mLayoutManager(std::make_unique<DescriptorSetLayoutManager>(device)),
mDescriptorPool(std::make_unique<DescriptorInfinitePool>(device)) {}
VulkanResourceAllocator* resourceAllocator)
: mImpl(new Impl(device, resourceAllocator)) {}
VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default;
void VulkanDescriptorSetManager::terminate() noexcept{
mLayoutManager.reset();
mDescriptorPool.reset();
clearHistory();
void VulkanDescriptorSetManager::terminate() noexcept {
assert_invariant(mImpl);
delete mImpl;
mImpl = nullptr;
}
// bind() is not really binding the set but just stashing until we have all the info
// (pipelinelayout).
void VulkanDescriptorSetManager::bind(uint8_t setIndex,
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
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::bind(uint8_t setIndex, VulkanDescriptorSet* set,
backend::DescriptorSetOffsetArray&& offsets) {
set->setOffsets(std::move(offsets));
mStashedSets[setIndex] = set;
}
void VulkanDescriptorSetManager::unbind(uint8_t setIndex) {
mStashedSets[setIndex] = {};
return mImpl->bind(setIndex, set, std::move(offsets));
}
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
VkPipelineLayout pipelineLayout, DescriptorSetMask const& 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,
};
mImpl->commit(commands, pipelineLayout, setMask);
}
void VulkanDescriptorSetManager::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
VkDeviceSize offset, VkDeviceSize size) noexcept {
VkDescriptorBufferInfo const info = {
.buffer = bufferObject->buffer.getGpuBuffer(),
.offset = offset,
.range = size,
};
VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
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::createSet(Handle<HwDescriptorSet> handle,
VulkanDescriptorSetLayout* layout) {
mImpl->createSet(handle, layout);
}
void VulkanDescriptorSetManager::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<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::destroySet(Handle<HwDescriptorSet> handle) {
mImpl->destroySet(handle);
}
void VulkanDescriptorSetManager::updateInputAttachment(
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
VulkanAttachment const& attachment) noexcept {
// TOOD: fill-in this region
}
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetManager::createSet(
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
auto const vkSet = mDescriptorPool->obtainSet(layout);
auto const& count = layout->count;
auto const vklayout = layout->getVkLayout();
return fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle, vkSet,
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
// down).
if (mDescriptorPool) {
mDescriptorPool->recycle(count, vklayout, vkSet);
}
});
}
void VulkanDescriptorSetManager::initVkLayout(
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
layout->setVkLayout(mLayoutManager->getVkLayout(layout->bitmask));
}
void VulkanDescriptorSetManager::clearHistory() {
mStashedSets = {};
}
} // namespace filament::backend
}// namespace filament::backend
@@ -17,10 +17,9 @@
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
#include "vulkan/VulkanHandles.h"
#include "vulkan/VulkanTexture.h"
#include "vulkan/VulkanUtility.h"
#include "vulkan/memory/ResourcePointer.h"
#include <vulkan/VulkanResourceAllocator.h>
#include <vulkan/VulkanTexture.h>
#include <vulkan/VulkanUtility.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
@@ -31,8 +30,6 @@
#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
@@ -47,55 +44,33 @@ public:
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
VulkanDescriptorSetManager(VkDevice device, fvkmemory::ResourceManager* resourceManager);
~VulkanDescriptorSetManager();
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
void terminate() noexcept;
void updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanBufferObject> bufferObject, VkDeviceSize offset,
VkDeviceSize size) noexcept;
void updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept;
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
void updateSampler(VulkanDescriptorSet* set, uint8_t binding,
VulkanTexture* texture, VkSampler sampler) noexcept;
void updateInputAttachment(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
VulkanAttachment const& attachment) noexcept;
void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept;
void bind(uint8_t setIndex, fvkmemory::resource_ptr<VulkanDescriptorSet> set,
backend::DescriptorSetOffsetArray&& offsets);
void unbind(uint8_t setIndex);
void bind(uint8_t setIndex, VulkanDescriptorSet* set, backend::DescriptorSetOffsetArray&& offsets);
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
DescriptorSetMask const& setMask);
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
VulkanBufferObject* bufferObject) noexcept;
void initVkLayout(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
void createSet(Handle<HwDescriptorSet> handle, VulkanDescriptorSetLayout* layout);
void clearHistory();
void destroySet(Handle<HwDescriptorSet> handle);
private:
class DescriptorSetLayoutManager;
class DescriptorInfinitePool;
using DescriptorSetArray =
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
VkDevice mDevice;
fvkmemory::ResourceManager* mResourceManager;
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;
class Impl;
Impl* mImpl;
};
}// namespace filament::backend
@@ -16,11 +16,12 @@
#include "VulkanPipelineLayoutCache.h"
#include <vulkan/VulkanResourceAllocator.h>
namespace filament::backend {
VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
DescriptorSetLayoutArray const& descriptorSetLayouts,
fvkmemory::resource_ptr<VulkanProgram> program) {
DescriptorSetLayoutArray const& descriptorSetLayouts, VulkanProgram* program) {
PipelineLayoutKey key = {};
uint8_t descSetLayoutCount = 0;
key.descSetLayouts = descriptorSetLayouts;
@@ -39,13 +40,13 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
for (uint8_t i = 0; i < pushConstantRangeCount; ++i) {
auto const& range = pushConstantRanges[i];
auto& pushConstant = key.pushConstant[i];
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
pushConstant.stage = static_cast<uint8_t>(ShaderStage::VERTEX);
}
if (range.stageFlags & VK_SHADER_STAGE_FRAGMENT_BIT) {
if (range.stageFlags & VK_SHADER_STAGE_FRAGMENT_BIT) {
pushConstant.stage = static_cast<uint8_t>(ShaderStage::FRAGMENT);
}
if (range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) {
if (range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) {
pushConstant.stage = static_cast<uint8_t>(ShaderStage::COMPUTE);
}
pushConstant.size = range.size;
@@ -30,7 +30,7 @@ class VulkanPipelineLayoutCache {
public:
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
VulkanPipelineLayoutCache(VkDevice device)
VulkanPipelineLayoutCache(VkDevice device, VulkanResourceAllocator* allocator)
: mDevice(device),
mTimestamp(0) {}
@@ -56,7 +56,7 @@ public:
// A pipeline layout depends on the descriptor set layout and the push constant ranges, which
// are described in the program.
VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts,
fvkmemory::resource_ptr<VulkanProgram> program);
VulkanProgram* program);
private:
using Timestamp = uint64_t;
@@ -1,129 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vulkan/memory/Resource.h"
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/VulkanHandles.h"
namespace filament::backend::fvkmemory {
template ResourceType getTypeEnum<VulkanBufferObject>() noexcept;
template ResourceType getTypeEnum<VulkanIndexBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanProgram>() noexcept;
template ResourceType getTypeEnum<VulkanRenderTarget>() noexcept;
template ResourceType getTypeEnum<VulkanSwapChain>() noexcept;
template ResourceType getTypeEnum<VulkanRenderPrimitive>() noexcept;
template ResourceType getTypeEnum<VulkanTexture>() noexcept;
template ResourceType getTypeEnum<VulkanTextureState>() noexcept;
template ResourceType getTypeEnum<VulkanTimerQuery>() noexcept;
template ResourceType getTypeEnum<VulkanVertexBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanVertexBufferInfo>() noexcept;
template ResourceType getTypeEnum<VulkanDescriptorSetLayout>() noexcept;
template ResourceType getTypeEnum<VulkanDescriptorSet>() noexcept;
template ResourceType getTypeEnum<VulkanFence>() noexcept;
template<typename D>
ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanBufferObject>) {
return ResourceType::BUFFER_OBJECT;
}
if constexpr (std::is_same_v<D, VulkanIndexBuffer>) {
return ResourceType::INDEX_BUFFER;
}
if constexpr (std::is_same_v<D, VulkanProgram>) {
return ResourceType::PROGRAM;
}
if constexpr (std::is_same_v<D, VulkanRenderTarget>) {
return ResourceType::RENDER_TARGET;
}
if constexpr (std::is_same_v<D, VulkanSwapChain>) {
return ResourceType::SWAP_CHAIN;
}
if constexpr (std::is_same_v<D, VulkanRenderPrimitive>) {
return ResourceType::RENDER_PRIMITIVE;
}
if constexpr (std::is_same_v<D, VulkanTexture>) {
return ResourceType::TEXTURE;
}
if constexpr (std::is_same_v<D, VulkanTextureState>) {
return ResourceType::TEXTURE_STATE;
}
if constexpr (std::is_same_v<D, VulkanTimerQuery>) {
return ResourceType::TIMER_QUERY;
}
if constexpr (std::is_same_v<D, VulkanVertexBuffer>) {
return ResourceType::VERTEX_BUFFER;
}
if constexpr (std::is_same_v<D, VulkanVertexBufferInfo>) {
return ResourceType::VERTEX_BUFFER_INFO;
}
if constexpr (std::is_same_v<D, VulkanDescriptorSetLayout>) {
return ResourceType::DESCRIPTOR_SET_LAYOUT;
}
if constexpr (std::is_same_v<D, VulkanDescriptorSet>) {
return ResourceType::DESCRIPTOR_SET;
}
if constexpr (std::is_same_v<D, VulkanFence>) {
return ResourceType::FENCE;
}
return ResourceType::UNDEFINED_TYPE;
}
std::string getTypeStr(ResourceType type) {
switch (type) {
case ResourceType::BUFFER_OBJECT:
return "BufferObject";
case ResourceType::INDEX_BUFFER:
return "IndexBuffer";
case ResourceType::PROGRAM:
return "Program";
case ResourceType::RENDER_TARGET:
return "RenderTarget";
case ResourceType::SWAP_CHAIN:
return "SwapChain";
case ResourceType::RENDER_PRIMITIVE:
return "RenderPrimitive";
case ResourceType::TEXTURE:
return "Texture";
case ResourceType::TEXTURE_STATE:
return "TextureState";
case ResourceType::TIMER_QUERY:
return "TimerQuery";
case ResourceType::VERTEX_BUFFER:
return "VertexBuffer";
case ResourceType::VERTEX_BUFFER_INFO:
return "VertexBufferInfo";
case ResourceType::DESCRIPTOR_SET_LAYOUT:
return "DescriptorSetLayout";
case ResourceType::DESCRIPTOR_SET:
return "DescriptorSet";
case ResourceType::FENCE:
return "Fence";
case ResourceType::UNDEFINED_TYPE:
return "";
}
}
void Resource::destroy(ResourceType type, HandleId id) {
resManager->destructLaterWithType(type, id);
}
void ThreadSafeResource::destroy(ResourceType type, HandleId id) {
resManager->destructLaterWithType(type, id);
}
} // namespace filament::backend::fvkmemory
@@ -1,143 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCE_H
#define TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCE_H
#include <private/backend/HandleAllocator.h>
#include <utils/Mutex.h>
#include <atomic>
#include <cstdint>
namespace filament::backend::fvkmemory {
using CounterIndex = int32_t;
using HandleId = HandleBase::HandleId;
class ResourceManager;
template <typename D>
struct resource_ptr;
// Subclasses of VulkanResource must provide this enum in their construction.
enum class ResourceType : uint8_t {
BUFFER_OBJECT = 0,
INDEX_BUFFER = 1,
PROGRAM = 2,
RENDER_TARGET = 3,
SWAP_CHAIN = 4,
RENDER_PRIMITIVE = 5,
TEXTURE = 6,
TEXTURE_STATE = 7,
TIMER_QUERY = 8,
VERTEX_BUFFER = 9,
VERTEX_BUFFER_INFO = 10,
DESCRIPTOR_SET_LAYOUT = 11,
DESCRIPTOR_SET = 12,
FENCE = 13,
UNDEFINED_TYPE = 14, // Must be the last enum because we use it for iterating over the enums.
};
template<typename D>
ResourceType getTypeEnum() noexcept;
std::string getTypeStr(ResourceType type);
inline bool isThreadSafeType(ResourceType type) {
return type == ResourceType::FENCE || type == ResourceType::TIMER_QUERY;
}
struct Resource {
Resource()
: resManager(nullptr),
id(HandleBase::nullid),
mCount(0),
restype(ResourceType::UNDEFINED_TYPE) {}
private:
inline void inc() noexcept {
mCount++;
}
inline void dec() noexcept {
assert_invariant(mCount > 0);
if (--mCount == 0) {
destroy(restype, id);
}
}
template <typename T>
inline void init(HandleId id, ResourceManager* resManager) {
this->id = id;
this->resManager = resManager;
this->restype = getTypeEnum<T>();
}
void destroy(ResourceType type, HandleId id);
ResourceManager* resManager; // 8
HandleId id; // 4
uint32_t mCount : 24;
ResourceType restype : 6; // restype + mCount is 4 bytes.
friend class ResourceManager;
template <typename D>
friend struct resource_ptr;
};
struct ThreadSafeResource {
ThreadSafeResource()
: resManager(nullptr),
id(HandleBase::nullid),
mCount(0),
restype(ResourceType::UNDEFINED_TYPE) {}
private:
inline void inc() noexcept {
mCount.fetch_add(1, std::memory_order_relaxed);
}
inline void dec() noexcept {
if (mCount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
destroy(restype, id);
}
}
template <typename T>
inline void init(HandleId id, ResourceManager* resManager) {
this->id = id;
this->resManager = resManager;
this->restype = getTypeEnum<T>();
}
void destroy(ResourceType type, HandleId id);
ResourceManager* resManager; // 8
HandleId id; // 4
std::atomic<uint32_t> mCount; // 4
ResourceType restype; // 1
friend class ResourceManager;
template <typename D>
friend struct resource_ptr;
};
} // namespace filament::backend::fvkmemory
#endif // TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCE_H
@@ -1,129 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/VulkanHandles.h"
#include <utils/Panic.h>
namespace filament::backend::fvkmemory {
namespace {
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
uint32_t COUNTER[(size_t) ResourceType::UNDEFINED_TYPE] = {};
#endif
}
ResourceManager::ResourceManager(size_t arenaSize, bool disableUseAfterFreeCheck)
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck) {}
void ResourceManager::gc() noexcept {
auto destroyAll = [this](GcList& list) {
for (auto const& [type, id]: list) {
destroyWithType(type, id);
}
list.clear();
};
{
// Note that we're not copying mThreadSafeGcList because the objects here do not have
// resource_ptrs to other handle objects, so their desctruction would not add more elements
// to mThreadSafeGcList.
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
destroyAll(mThreadSafeGcList);
}
GcList gcs;
std::swap(gcs, mGcList);
destroyAll(gcs);
}
void ResourceManager::terminate() noexcept {
while (!mThreadSafeGcList.empty() || !mGcList.empty()) {
gc();
}
}
void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
switch (type) {
case ResourceType::BUFFER_OBJECT:
destruct<VulkanBufferObject>(Handle<VulkanBufferObject>(id));
break;
case ResourceType::INDEX_BUFFER:
destruct<VulkanIndexBuffer>(Handle<VulkanIndexBuffer>(id));
break;
case ResourceType::PROGRAM:
destruct<VulkanProgram>(Handle<VulkanProgram>(id));
break;
case ResourceType::RENDER_TARGET:
destruct<VulkanRenderTarget>(Handle<VulkanRenderTarget>(id));
break;
case ResourceType::SWAP_CHAIN:
destruct<VulkanSwapChain>(Handle<VulkanSwapChain>(id));
break;
case ResourceType::RENDER_PRIMITIVE:
destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;
case ResourceType::TEXTURE:
destruct<VulkanTexture>(Handle<VulkanTexture>(id));
break;
case ResourceType::TEXTURE_STATE:
destruct<VulkanTextureState>(Handle<VulkanTextureState>(id));
break;
case ResourceType::TIMER_QUERY:
destruct<VulkanTimerQuery>(Handle<VulkanTimerQuery>(id));
break;
case ResourceType::VERTEX_BUFFER:
destruct<VulkanVertexBuffer>(Handle<VulkanVertexBuffer>(id));
break;
case ResourceType::VERTEX_BUFFER_INFO:
destruct<VulkanVertexBufferInfo>(Handle<VulkanVertexBufferInfo>(id));
break;
case ResourceType::DESCRIPTOR_SET_LAYOUT:
destruct<VulkanDescriptorSetLayout>(Handle<VulkanDescriptorSetLayout>(id));
break;
case ResourceType::DESCRIPTOR_SET:
destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
break;
case ResourceType::FENCE:
destruct<VulkanFence>(Handle<VulkanFence>(id));
break;
case ResourceType::UNDEFINED_TYPE:
break;
}
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
COUNTER[(size_t) type]--;
#endif
}
void ResourceManager::traceConstruction(ResourceType type, HandleId id) {
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
assert_invariant(type != ResourceType::UNDEFINED_TYPE);
COUNTER[(size_t) type]++;
#endif
}
void ResourceManager::print() const noexcept {
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
utils::slog.e << "-------------------" << utils::io::endl;
for (size_t i = 0; i < (size_t) ResourceType::UNDEFINED_TYPE; ++i) {
utils::slog.e <<" " << getTypeStr((ResourceType) i) << "=" << COUNTER[i] << utils::io::endl;
}
utils::slog.e << "+++++++++++++++++++" << utils::io::endl;
#endif
}
}
@@ -1,111 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEMANAGER_H
#define TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEMANAGER_H
#include "vulkan/memory/Resource.h"
#include "vulkan/VulkanAsyncHandles.h"
#include <private/backend/HandleAllocator.h>
#include <utils/Panic.h>
namespace filament::backend::fvkmemory {
class ResourceManager {
public:
ResourceManager(size_t arenaSize, bool disableUseAfterFreeCheck);
template<typename D>
inline Handle<D> allocHandle() noexcept {
return mHandleAllocatorImpl.allocate<D>();
}
inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
}
void gc() noexcept;
void print() const noexcept;
void terminate() noexcept;
private:
using AllocatorImpl = HandleAllocatorVK;
template<typename D>
using requires_thread_safety = typename std::disjunction<std::is_same<D, VulkanFence>,
std::is_same<D, VulkanTimerQuery>>;
template<typename D, typename B, typename... ARGS>
inline D* construct(Handle<B> const& handle, ARGS&&... args) noexcept {
constexpr bool THREAD_SAFETY = requires_thread_safety<D>::value;
D* obj = mHandleAllocatorImpl.construct<D, B>(handle, std::forward<ARGS>(args)...);
if constexpr (THREAD_SAFETY) {
((ThreadSafeResource*) obj)->template init<D>(handle.getId(), this);
} else {
((Resource*) obj)->template init<D>(handle.getId(), this);
}
traceConstruction(getTypeEnum<D>(), handle.getId());
return obj;
}
template<typename Dp, typename B>
inline typename std::enable_if_t<
std::is_pointer_v<Dp> && std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B> const& handle) noexcept {
return mHandleAllocatorImpl.handle_cast<Dp, B>(handle);
}
template<typename D, typename B>
inline void destruct(Handle<B> handle) noexcept {
D* obj = handle_cast<D*>(handle);
mHandleAllocatorImpl.deallocate(handle, obj);
}
// This will post the destruction to a list that will be processed when gc is called from the
// backend thread.
inline void destructLaterWithType(ResourceType type, HandleId id) {
if (isThreadSafeType(type)) {
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
mThreadSafeGcList.push_back({type, id});
} else {
mGcList.push_back({type, id});
}
}
void destroyWithType(ResourceType type, HandleId id);
void traceConstruction(ResourceType type, HandleId id);
AllocatorImpl mHandleAllocatorImpl;
using GcList = std::vector<std::pair<ResourceType, HandleId>>;
utils::Mutex mThreadSafeGcListMutex;
GcList mThreadSafeGcList;
GcList mGcList;
template<typename D>
friend struct resource_ptr;
friend struct Resource;
friend struct ThreadSafeResource;
};
} // namespace filament::backend::fvkmemory
#endif // TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEMANAGER_H
@@ -1,194 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEPOINTER_H
#define TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEPOINTER_H
#include "vulkan/memory/Resource.h"
#include "vulkan/memory/ResourceManager.h"
#include <backend/Handle.h>
#include <utility>
namespace filament::backend {
class VulkanDriver;
namespace fvkmemory {
// This is a ref-counting, reference holder class (similar to std::shared_ptr) that serves as the
// primary interface between the vk backend (VulkanDriver) and the handle allocator. All objects
// that are allocated from the handle allocator are expected to be contained within a
template<typename D>
struct resource_ptr {
private:
template<typename B>
using enabled_resource_ptr =
resource_ptr<typename std::enable_if<std::is_base_of<B, D>::value, D>::type>;
public:
template<typename B, typename... ARGS>
static enabled_resource_ptr<B> make(ResourceManager* resManager, Handle<B> const& handle,
ARGS&&... args) noexcept {
D* ptr = resManager->construct<D, B>(handle, std::forward<ARGS>(args)...);
return {ptr};
}
// This will alloc a handle and then construct the object.
template<typename... ARGS>
static resource_ptr<D> construct(ResourceManager* resManager, ARGS&&... args) noexcept {
auto handle = resManager->allocHandle<D>();
D* ptr = resManager->construct<D, D>(handle, std::forward<ARGS>(args)...);
return {ptr};
}
template<typename B>
static enabled_resource_ptr<B> cast(ResourceManager* resManager,
Handle<B> const& handle) noexcept {
D* ptr = resManager->handle_cast<D*, B>(handle);
return {ptr};
}
template<typename B>
static enabled_resource_ptr<B> cast(B* ptr) noexcept {
return {ptr};
}
~resource_ptr() {
if (mRef) {
mRef->dec();
}
}
inline resource_ptr() = default;
// move constructor operator
inline resource_ptr(resource_ptr<D>&& rhs) {
(*this) = std::move(rhs);
}
inline resource_ptr(resource_ptr<D> const& rhs) {
(*this) = rhs;
}
template<typename E>
using is_supported_t =
typename std::enable_if<std::is_base_of<D, E>::value>::type;
template<typename E, typename = is_supported_t<E>>
inline resource_ptr(resource_ptr<E> const& rhs) {
(*this) = rhs;
}
// move operator
inline resource_ptr<D>& operator=(resource_ptr<D> && rhs) {
std::swap(mRef, rhs.mRef);
return *this;
}
inline resource_ptr<D>& operator=(resource_ptr<D> const& rhs) {
if (mRef == rhs.mRef) {
return *this;
}
if (mRef) {
mRef->dec();
}
mRef = rhs.mRef;
mRef->inc();
return *this;
}
template<typename E, typename = is_supported_t<E>>
inline resource_ptr<D>& operator=(resource_ptr<E> const& rhs) {
if (mRef == rhs.mRef) {
return *this;
}
if (mRef) {
mRef->dec();
}
mRef = rhs.mRef;
mRef->inc();
return *this;
}
inline bool operator==(resource_ptr<D> const& other) const {
return id() == other.id() && type() == other.type();
}
inline explicit operator bool() const {
return bool(mRef);
}
inline D* operator->() {
return get();
}
inline D const* operator->() const {
return get();
}
inline HandleId id() const {
if (mRef) {
return mRef->id;
}
return HandleBase::nullid;
}
inline D* get() const {
assert_invariant(mRef);
return mRef;
}
private:
// inc() and dec() For tracking ref-count with respect to create/destroy backend APIs. They can
// only be used from VulkanDriver.
inline void dec() {
assert_invariant(mRef);
mRef->dec();
}
inline void inc() {
assert_invariant(mRef);
mRef->inc();
}
inline ResourceType type() const {
if (mRef) {
return mRef->restype;
}
return ResourceType::UNDEFINED_TYPE;
}
resource_ptr(D* ref)
: mRef(ref) {
mRef->inc();
}
D* mRef = nullptr;
// Allow generic structures to hold resources of different types. For example,
// std::vector<fvkmemory::Resource> resources;
friend struct resource_ptr<Resource>;
friend struct resource_ptr<ThreadSafeResource>;
// This enables access to resource_ptr's private inc() and dec() methods.
friend class filament::backend::VulkanDriver;
};
} // namespace fvkmemory
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKAN_MEMORY_RESOURCEPOINTER_H

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