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
142 changed files with 1262 additions and 3019 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.55.1'
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.55.1'
pod 'Filament', '~> 1.54.2'
```
## Documentation
-13
View File
@@ -7,19 +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.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
@@ -520,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,
@@ -534,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,
@@ -542,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);
}
@@ -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;
}
@@ -418,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.
@@ -431,12 +432,12 @@ public class Engine {
*/
public long resourceAllocatorCacheMaxAge = 1;
/**
/*
* Disable backend handles use-after-free checks.
*/
public boolean disableHandleUseAfterFreeCheck = false;
/**
/*
* Sets a preferred shader language for Filament to use.
*
* The Metal backend supports two shader languages: MSL (Metal Shading Language) and
@@ -458,19 +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
*/
public boolean assertNativeWindowIsValid = false;
}
private Engine(long nativeEngine, Config config) {
@@ -1412,11 +1406,12 @@ 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);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.55.1
VERSION_NAME=1.54.2
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+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.
-2
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
-12
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)
@@ -368,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()
@@ -393,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}>
)
@@ -403,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}>
)
@@ -438,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
@@ -536,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
+3 -13
View File
@@ -242,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 {
@@ -765,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
@@ -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
+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
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;
@@ -184,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;
+33 -43
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));
}
@@ -664,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));
@@ -809,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) {
@@ -859,6 +854,12 @@ void MetalDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
}
void MetalDriver::terminate() {
// Terminate any outstanding MetalTextures.
while (!mContext->texturesToDestroy.empty()) {
Handle<HwTexture> toDestroy = mContext->texturesToDestroy.pop();
destruct_handle<MetalTexture>(toDestroy);
}
// finish() will flush the pending command buffer and will ensure all GPU work has finished.
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
finish();
@@ -1082,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));
}
@@ -1605,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);
@@ -1797,7 +1787,7 @@ void MetalDriver::bindDescriptorSet(
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) {
@@ -1806,7 +1796,7 @@ void MetalDriver::bindDescriptorSet(
offsetsVector[i] = offsets[i];
}
printf("])\n");
DEBUG_LOG("])\n");
#endif
// Bind the descriptor set.
+26 -10
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:
@@ -271,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;
@@ -287,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 {
@@ -495,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
+30 -42
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,
@@ -650,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;
}
@@ -1353,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.
@@ -1376,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)) {
@@ -1429,7 +1417,7 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
}
}
return buffer.get();
return buffer;
}
} // namespace backend
+1 -1
View File
@@ -434,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) {
}
@@ -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);
@@ -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
+18 -27
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;
}
@@ -937,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;
}
}
@@ -958,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",
""},
+10 -16
View File
@@ -1266,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
@@ -1277,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;
}
}
@@ -1297,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);
@@ -1321,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
@@ -1331,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)
{
@@ -1435,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
@@ -1732,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);
}
}
@@ -1745,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);
}
@@ -2914,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
@@ -2929,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);
}
@@ -2944,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);
}
@@ -2962,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
@@ -3467,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()
}
@@ -3892,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());
}
@@ -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
+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 << "}";
}
@@ -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);
+1 -5
View File
@@ -43,6 +43,7 @@ struct VulkanCommandBuffer;
struct VulkanAttachment {
VulkanTexture* texture = nullptr;
uint8_t level = 0;
uint8_t baseViewIndex = 0;
uint8_t layerCount = 1;
uint16_t layer = 0;
@@ -144,10 +145,6 @@ public:
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
}
inline bool isLazilyAllocatedMemorySupported() const noexcept {
return mLazilyAllocatedMemorySupported;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties mPhysicalDeviceProperties = {};
@@ -155,7 +152,6 @@ private:
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mMultiviewEnabled = false;
bool mLazilyAllocatedMemorySupported = false;
VkFormatList mDepthStencilFormats;
VkFormatList mBlittableDepthStencilFormats;
+62 -36
View File
@@ -88,6 +88,28 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
return allocator;
}
VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) {
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
stagePool, true /* heap allocated */);
uint32_t black = 0;
PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
emptyTexture->updateImage(pbd, 1, 1, 1, 0, 0, 0, 0);
return emptyTexture;
}
VulkanBufferObject* createEmptyBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanCommands* commands) {
VulkanBufferObject* obj =
new VulkanBufferObject(allocator, stagePool, 1, BufferObjectBinding::UNIFORM);
uint8_t byte = 0;
obj->buffer.loadFromCpu(commands->get().buffer(), &byte, 0, 1);
return obj;
}
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
@@ -233,6 +255,13 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
#endif
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool);
mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands);
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
mEmptyBufferObject);
}
VulkanDriver::~VulkanDriver() noexcept = default;
@@ -297,6 +326,9 @@ void VulkanDriver::terminate() {
// to those commands are no longer referenced.
finish(0);
delete mEmptyBufferObject;
delete mEmptyTexture;
// Command buffers should come first since it might have commands depending on resources that
// are about to be destroyed.
mCommands.terminate();
@@ -367,8 +399,8 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
FVK_SYSTRACE_END();
}
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
FrameScheduledCallback&& callback, uint64_t flags) {
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, FrameScheduledCallback&& callback) {
}
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
@@ -404,6 +436,14 @@ void VulkanDriver::updateDescriptorSetTexture(
SamplerParams params) {
VulkanDescriptorSet* set = mResourceAllocator.handle_cast<VulkanDescriptorSet*>(dsh);
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(th);
// We need to make sure the initial layout transition has been completed before we can write
// the sampler descriptor. We flush and wait until the transition has been completed.
if (!texture->transitionReady()) {
mCommands.flush();
mCommands.wait();
}
VkSampler const vksampler = mSamplerCache.getSampler(params);
mDescriptorSetManager.updateSampler(set, binding, texture, vksampler);
}
@@ -607,6 +647,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorTargets[i] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
.level = color[i].level,
.baseViewIndex = color[i].baseViewIndex,
.layerCount = layerCount,
.layer = color[i].layer,
};
@@ -622,6 +663,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[0] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
.level = depth.level,
.baseViewIndex = depth.baseViewIndex,
.layerCount = layerCount,
.layer = depth.layer,
};
@@ -635,6 +677,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthStencil[1] = {
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
.level = stencil.level,
.baseViewIndex = stencil.baseViewIndex,
.layerCount = layerCount,
.layer = stencil.layer,
};
@@ -706,10 +749,7 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) {
VulkanDescriptorSetLayout* layout = mResourceAllocator.construct<VulkanDescriptorSetLayout>(
dslh, info);
// This will create a VkDescriptorSetLayout (which is cached) for this object.
mDescriptorSetManager.initVkLayout(layout);
dslh, mPlatform->getDevice(), info);
mResourceManager.acquire(layout);
}
@@ -1248,12 +1288,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// the non-sampling case.
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer const cmdbuffer = commands.buffer();
// Scissor is reset with each render pass
// This also takes care of VUID-vkCmdDrawIndexed-None-07832.
VkRect2D const scissor{ .offset = { 0, 0 }, .extent = extent };
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
VulkanLayout currentDepthLayout = depth.getLayout();
TargetBufferFlags clearVal = params.flags.clear;
@@ -1275,14 +1309,13 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = {
.initialColorLayoutMask = 0,
.initialDepthLayout = currentDepthLayout,
.depthFormat = depth.getFormat(),
.clear = clearVal,
.discardStart = discardStart,
.discardEnd = discardEndVal,
.initialDepthLayout = currentDepthLayout,
.samples = rt->getSamples(),
.needsResolveMask = 0,
.usesLazilyAllocatedMemory = 0,
.subpassMask = uint8_t(params.subpassMask),
.viewCount = renderTargetLayerCount,
};
@@ -1290,15 +1323,10 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
VulkanAttachment const& info = rt->getColor(i);
if (info.texture) {
assert_invariant(info.layerCount == renderTargetLayerCount);
rpkey.initialColorLayoutMask |= 1 << i;
rpkey.colorFormat[i] = info.getFormat();
if (rpkey.samples > 1) {
const VulkanTexture* sidecar = info.texture->getSidecar();
if (sidecar && sidecar->isTransientAttachment()) {
rpkey.usesLazilyAllocatedMemory |= (1 << i);
}
if (info.texture->samples == 1) {
rpkey.needsResolveMask |= (1 << i);
}
if (rpkey.samples > 1 && info.texture->samples == 1) {
rpkey.needsResolveMask |= (1 << i);
}
} else {
rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED;
@@ -1327,15 +1355,9 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
if (fbkey.samples == 1) {
auto const& range = attachment.getSubresourceRange();
auto tex = attachment.texture;
if (tex->getLayout(range.baseMipLevel, range.baseArrayLayer) !=
VulkanLayout::COLOR_ATTACHMENT &&
!tex->transitionLayout(&commands, range, VulkanLayout::COLOR_ATTACHMENT)) {
// If the layout transition did not emit a barrier, we do it manually here.
tex->samplerToAttachmentBarrier(&commands, range);
}
attachment.texture->transitionLayout(&commands,
range, VulkanLayout::COLOR_ATTACHMENT);
renderPassAttachments.insert(attachment);
fbkey.color[i] = attachment.getImageView();
fbkey.resolve[i] = VK_NULL_HANDLE;
assert_invariant(fbkey.color[i]);
@@ -1350,6 +1372,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
VulkanTexture* texture = attachment.texture;
if (texture->samples == 1) {
mRenderPassFboInfo.hasColorResolve = true;
auto const& range = attachment.getSubresourceRange();
attachment.texture->transitionLayout(&commands,
range, VulkanLayout::COLOR_ATTACHMENT);
@@ -1492,7 +1516,7 @@ void VulkanDriver::endRenderPass(int) {
}
}
mRenderPassFboInfo = {};
mRenderPassFboInfo.clear();
mCurrentRenderPass.renderTarget = nullptr;
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
FVK_SYSTRACE_END();
@@ -1806,7 +1830,7 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
}
auto layout = mResourceAllocator.handle_cast<VulkanDescriptorSetLayout*>(handle);
layoutCount++;
return layout->getVkLayout();
return layout->vklayout;
});
auto pipelineLayout = mPipelineLayoutCache.getLayout(layoutList, program);
@@ -1821,6 +1845,11 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
mPipelineCache.bindLayout(pipelineLayout);
mPipelineCache.bindPipeline(commands);
// Since we don't statically define scissor as part of the pipeline, we need to call scissor at
// least once. Context: VUID-vkCmdDrawIndexed-None-07832.
auto const& extent = rt->getExtent();
scissor({0, 0, extent.width, extent.height});
FVK_SYSTRACE_END();
}
@@ -1899,9 +1928,6 @@ void VulkanDriver::scissor(Viewport scissorBox) {
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
// TODO: it's a common case that scissor() is called with (0, 0, maxint, maxint)
// we should maybe have a fast path for this and avoid vkCmdSetScissor() if possible
// Set scissoring.
// clamp left-bottom to 0,0 and avoid overflows
constexpr int32_t maxvali = std::numeric_limits<int32_t>::max();
+23 -9
View File
@@ -47,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,
@@ -115,6 +130,10 @@ private:
VulkanPlatform* mPlatform = nullptr;
std::unique_ptr<VulkanTimestamps> mTimestamps;
// Placeholder resources
VulkanTexture* mEmptyTexture;
VulkanBufferObject* mEmptyBufferObject;
VulkanSwapChain* mCurrentSwapChain = nullptr;
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
VulkanRenderPass mCurrentRenderPass = {};
@@ -141,18 +160,13 @@ private:
VulkanDescriptorSetManager mDescriptorSetManager;
// This is necessary for us to write to push constants after binding a pipeline.
struct {
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;
@@ -255,10 +255,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey 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),
};
}
+15 -4
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.");
+74 -13
View File
@@ -17,10 +17,6 @@
#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"
@@ -66,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:
@@ -77,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;
@@ -108,13 +166,21 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
} // anonymous namespace
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
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)) {}
count(Count::fromLayoutBitmask(bitmask)) {
}
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
}
void VulkanDescriptorSet::acquire(VulkanTexture* texture) {
mResources.acquire(texture);
mTextures[mTextureCount++] = texture;
}
void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) {
@@ -282,16 +348,11 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
if (texture && texture->samples == 1) {
auto msTexture = texture->getSidecar();
if (UTILS_UNLIKELY(!msTexture)) {
// Clear all usage flags that are not related to attachments, so that we can
// use the transient usage flag.
const TextureUsage usage = texture->usage & TextureUsage::ALL_ATTACHMENTS;
assert_invariant(static_cast<uint16_t>(usage) != 0U);
// TODO: This should be allocated with the ResourceAllocator.
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
handleAllocator,
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
samples, texture->width, texture->height, texture->depth, usage,
samples, texture->width, texture->height, texture->depth, texture->usage,
stagePool, true /* heap allocated */);
texture->setSidecar(msTexture);
}
+26 -15
View File
@@ -41,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();
}
@@ -114,25 +114,21 @@ struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout
}
};
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 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(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
OnRecycle&& onRecycleFn)
@@ -143,7 +139,7 @@ public:
~VulkanDescriptorSet() {
if (mOnRecycleFn) {
mOnRecycleFn(this);
mOnRecycleFn();
}
}
@@ -151,9 +147,17 @@ public:
void acquire(VulkanBufferObject* texture);
bool hasTexture(VulkanTexture* texture) {
return std::any_of(mTextures.begin(), mTextures.end(),
[texture](auto t) { return t == texture; });
}
// TODO: maybe change to fixed size for performance.
VkDescriptorSet const vkSet;
private:
std::array<VulkanTexture*, 16> mTextures = { nullptr };
uint8_t mTextureCount = 0;
VulkanAcquireOnlyResourceManager mResources;
OnRecycle mOnRecycleFn;
};
@@ -194,6 +198,10 @@ struct VulkanProgram : public HwProgram, VulkanResource {
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
inline utils::FixedCapacityVector<uint16_t> const& getBindingToSamplerIndex() const {
return mInfo->bindingToSamplerIndex;
}
// Get a list of the sampler binding indices so that we don't have to loop through all possible
// samplers.
inline BindingList const& getBindings() const { return mInfo->bindings; }
@@ -224,7 +232,8 @@ struct VulkanProgram : public HwProgram, VulkanResource {
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
@@ -232,6 +241,8 @@ private:
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;
@@ -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:
@@ -232,7 +232,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
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 = {
@@ -268,7 +268,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
// Restore the source image layout.
srcTexture->transitionLayout(cmdbuffer, srcRange, srcTexture->getDefaultLayout());
srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT);
vkEndCommandBuffer(cmdbuffer);
+50 -144
View File
@@ -88,76 +88,32 @@ 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)
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
mVkFormat(format),
mViewType(viewType),
mFullViewRange{filament::backend::getImageAspect(format), 0, levels, 0, layerCount},
mDefaultLayout(defaultLayout),
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) {
}
VulkanTextureState* VulkanTexture::getSharedState() {
VulkanTextureState* state = mAllocator->handle_cast<VulkanTextureState*>(mState);
@@ -169,7 +125,6 @@ VulkanTextureState const* VulkanTexture::getSharedState() const {
return state;
}
// Constructor for internally passed VkImage
VulkanTexture::VulkanTexture(
VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
@@ -182,14 +137,12 @@ VulkanTexture::VulkanTexture(
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(
device, allocator, commands, stagePool,
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1,
getDefaultLayoutImpl(tusage))) {
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, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels,
@@ -201,7 +154,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
getLayerCount(target, depth), VulkanLayout::UNDEFINED)) {
getLayerCount(target, depth))) {
auto* const state = getSharedState();
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
@@ -240,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);
state->mIsTransientAttachment = useTransientAttachment;
const VkImageUsageFlags transientFlag =
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
if (any(usage & TextureUsage::SAMPLEABLE)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
@@ -268,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) {
@@ -329,11 +269,8 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs);
const VkFlags requiredMemoryFlags =
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U);
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.";
@@ -356,20 +293,18 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
commandsBuf.acquire(this);
auto const defaultLayout = state->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
transitionLayout(&commandsBuf, mPrimaryViewRange, defaultLayout);
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,
VulkanResourceAllocator* handleAllocator, VulkanTexture const* src, uint8_t baseLevel,
uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator)
{
mState = src->mState;
auto* state = getSharedState();
@@ -379,15 +314,14 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
mPrimaryViewRange.levelCount = levelCount;
}
// Constructor for creating a texture view with swizzle
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator, VulkanTexture const* src,
VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
VulkanResource(VulkanResourceType::TEXTURE),
mAllocator(handleAllocator) {
mState = src->mState;
auto* state = getSharedState();
state->refs++;
@@ -490,7 +424,7 @@ 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);
@@ -541,11 +475,6 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
transitionLayout(&commands, range, oldLayout);
}
VulkanLayout VulkanTexture::getDefaultLayout() const {
auto* const state = getSharedState();
return state->mDefaultLayout;
}
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
range.levelCount = 1;
range.layerCount = 1;
@@ -591,11 +520,13 @@ VkImageAspectFlags VulkanTexture::getImageAspect() const {
}
bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range, VulkanLayout newLayout) {
return transitionLayout(commands->buffer(), range, newLayout);
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);
@@ -650,11 +581,10 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
});
}
// 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 << ","
@@ -673,30 +603,6 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
return hasTransitions;
}
void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range) {
VkCommandBuffer const cmdbuf = commands->buffer();
auto* const state = getSharedState();
VkImageLayout const layout =
imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
VkImageMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = layout,
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = state->mTextureImage,
.subresourceRange = range,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier);
}
void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
VkImageSubresourceRange const& range) {
VkCommandBuffer const cmdbuf = commands->buffer();
@@ -757,9 +663,9 @@ VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
void VulkanTexture::print() const {
auto* const state = getSharedState();
uint32_t const firstLayer = 0;
uint32_t const lastLayer = firstLayer + state->mFullViewRange.layerCount;
uint32_t const lastLayer = firstLayer + mFullViewRange.layerCount;
uint32_t const firstLevel = 0;
uint32_t const lastLevel = firstLevel + state->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) {
+14 -19
View File
@@ -34,8 +34,8 @@ class VulkanResourceAllocator;
struct VulkanTextureState : public VulkanResource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
uint8_t layerCount, VulkanLayout defaultLayout);
VulkanStagePool& stagePool,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount);
struct ImageViewKey {
VkImageSubresourceRange range; // 4 * 5 bytes
@@ -67,8 +67,8 @@ struct VulkanTextureState : public VulkanResource {
VkFormat const mVkFormat;
VkImageViewType const mViewType;
VkImageSubresourceRange const mFullViewRange;
VkImage mTextureImage = VK_NULL_HANDLE;
VulkanLayout mDefaultLayout;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
@@ -78,7 +78,7 @@ struct VulkanTextureState : public VulkanResource {
VkDevice mDevice;
VmaAllocator mAllocator;
VulkanCommands* mCommands;
bool mIsTransientAttachment;
std::shared_ptr<VulkanCmdFence> mTransitionFence;
};
@@ -134,10 +134,6 @@ struct VulkanTexture : public HwTexture, VulkanResource {
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
}
// Returns the layout for the intended use of this texture (and not the expected layout at the
// time of the call.
VulkanLayout getDefaultLayout() const;
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
// and the identity swizzle.
@@ -174,23 +170,15 @@ struct VulkanTexture : public HwTexture, VulkanResource {
return state->mSidecarMSAA.get();
}
bool isTransientAttachment() const {
VulkanTextureState const* state = getSharedState();
return state->mIsTransientAttachment;
}
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;
@@ -199,6 +187,13 @@ struct VulkanTexture : public HwTexture, VulkanResource {
// manually (outside of calls to transitionLayout).
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
bool transitionReady() {
VulkanTextureState* state = getSharedState();
auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS;
state->mTransitionFence.reset();
return res;
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const;
#endif
@@ -26,7 +26,6 @@
#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;
@@ -275,13 +196,12 @@ public:
: mDevice(device) {}
VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) {
auto const vklayout = layout->getVkLayout();
DescriptorPool* sameTypePool = nullptr;
for (auto& pool: mPools) {
if (!pool->canAllocate(layout->count)) {
continue;
}
if (auto set = pool->obtainSet(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,253 +240,315 @@ private:
std::vector<std::unique_ptr<DescriptorPool>> mPools;
};
class VulkanDescriptorSetManager::DescriptorSetLayoutManager {
public:
DescriptorSetLayoutManager(VkDevice device)
: mDevice(device) {}
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout* layout) {
auto const& bitmasks = layout->bitmask;
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
return itr->second;
}
auto vklayout = createLayout(mDevice, layout->bitmask);
mVkLayouts[layout->bitmask] = vklayout;
return vklayout;
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));
}
~DescriptorSetLayoutManager() {
for (auto& itr: mVkLayouts) {
vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC);
}
}
private:
VkDevice mDevice;
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
mVkLayouts;
};
class VulkanDescriptorSetManager::DescriptorSetHistory {
} // anonymous namespace
class VulkanDescriptorSetManager::Impl {
private:
using TextureBundle = std::pair<VulkanTexture*, VkImageSubresourceRange>;
struct DescriptorSetHistory {
private:
using TextureBundle = std::pair<VulkanTexture*, VkImageSubresourceRange>;
public:
DescriptorSetHistory() : mResources(nullptr) {}
DescriptorSetHistory(BitmaskGroup const& mask, VulkanResourceAllocator* allocator,
VulkanDescriptorSet* set)
: dynamicUboMask(mask.dynamicUbo),
mResources(allocator),
mSet(set),
mBound(false) {
// initial state is unbound.
mResources.acquire(mSet);
unbind();
}
~DescriptorSetHistory() {
assert_invariant(mSet);
mResources.clear();
}
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets);
mBound = false;
}
void write(uint8_t binding) noexcept {
mBound = false;
}
// Ownership will be transfered to the commandbuffer.
void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, uint8_t index) noexcept {
VkCommandBuffer const cmdbuffer = commands->buffer();
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
index, 1, &mSet->vkSet, (uint32_t) dynamicUboMask.count(), mOffsets.data());
commands->acquire(mSet);
mResources.clear();
mBound = true;
}
void unbind() noexcept {
mResources.acquire(mSet);
mBound = false;
}
bool bound() const noexcept { return mBound; }
UniformBufferBitmask const dynamicUboMask;
private:
FixedSizeVulkanResourceManager<1> mResources;
VulkanDescriptorSet* mSet = nullptr;
backend::DescriptorSetOffsetArray mOffsets;
bool mBound = false;
};
using DescriptorSetHistoryArray =
std::array<DescriptorSetHistory*, UNIQUE_DESCRIPTOR_SET_COUNT>;
struct BoundInfo {
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
DescriptorSetMask setMask;
DescriptorSetHistoryArray boundSets;
bool operator==(BoundInfo const& info) const {
if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) {
return false;
}
bool equal = true;
setMask.forEachSetBit([&](size_t i) {
if (boundSets[i] != info.boundSets[i]) {
equal = false;
}
});
return equal;
}
};
public:
DescriptorSetHistory()
: dynamicUboCount(0),
mResources(nullptr) {}
Impl(VkDevice device, VulkanResourceAllocator* resourceAllocator)
: mDevice(device),
mResourceAllocator(resourceAllocator),
mDescriptorPool(device) {}
DescriptorSetHistory(UniformBufferBitmask const& dynamicUbo, uint8_t uniqueDynamicUboCount,
VulkanResourceAllocator* allocator, VulkanDescriptorSet* set)
: dynamicUboMask(dynamicUbo),
dynamicUboCount(uniqueDynamicUboCount),
mResources(allocator),
mSet(set),
mBound(false) {
assert_invariant(set);
// initial state is unbound.
unbind();
// 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;
}
~DescriptorSetHistory() {
if (mSet) {
mResources.clear();
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;
}
}
}
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets);
mBound = false;
}
void write(uint8_t binding) noexcept { mBound = false; }
// Ownership will be transfered to the commandbuffer.
void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
uint8_t index) noexcept {
VkCommandBuffer const cmdbuffer = commands->buffer();
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &mSet->vkSet, dynamicUboCount, mOffsets.data());
commands->acquire(mSet);
mResources.clear();
mBound = true;
}
void unbind() noexcept {
mResources.acquire(mSet);
mBound = false;
}
bool bound() const noexcept { return mBound; }
UniformBufferBitmask const dynamicUboMask;
uint8_t const dynamicUboCount;
private:
FixedSizeVulkanResourceManager<1> mResources;
VulkanDescriptorSet* mSet = nullptr;
backend::DescriptorSetOffsetArray mOffsets;
bool mBound = false;
VkDevice mDevice;
VulkanResourceAllocator* mResourceAllocator;
DescriptorInfinitePool mDescriptorPool;
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
std::unordered_map<VkDescriptorSet, DescriptorSetHistory> mHistory;
DescriptorSetHistoryArray mStashedSets;
BoundInfo mLastBoundInfo;
VkDescriptorBufferInfo mPlaceHolderBufferInfo;
VkDescriptorImageInfo mPlaceHolderImageInfo;
};
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
VulkanResourceAllocator* resourceAllocator)
: mDevice(device),
mResourceAllocator(resourceAllocator),
mLayoutManager(std::make_unique<DescriptorSetLayoutManager>(device)),
mDescriptorPool(std::make_unique<DescriptorInfinitePool>(device)) {}
: mImpl(new Impl(device, resourceAllocator)) {}
VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default;
void VulkanDescriptorSetManager::terminate() noexcept{
mLayoutManager.reset();
mDescriptorPool.reset();
mHistory.clear();
}
// bind() is not really binding the set but just stashing until we have all the info
// (pipelinelayout).
void VulkanDescriptorSetManager::bind(uint8_t setIndex, VulkanDescriptorSet* set,
backend::DescriptorSetOffsetArray&& offsets) {
auto history = mHistory[set].get();
history->setOffsets(std::move(offsets));
auto lastHistory = mStashedSets[setIndex];
if (lastHistory) {
lastHistory->unbind();
}
mStashedSets[setIndex] = history;
}
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) {
DescriptorSetHistoryArray& updateSets = mStashedSets;
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
// actual set of sets that *needs* to be bound.
DescriptorSetMask curMask = setMask;
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 VulkanDescriptorSetManager::terminate() noexcept {
assert_invariant(mImpl);
delete mImpl;
mImpl = nullptr;
}
void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
VkDescriptorBufferInfo const info = {
.buffer = bufferObject->buffer.getGpuBuffer(),
.offset = offset,
.range = size,
};
VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
auto& history = mHistory[set];
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);
mImpl->updateBuffer(set, binding, bufferObject, offset, size);
}
void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding,
VulkanTexture* texture, VkSampler sampler) noexcept {
VkDescriptorImageInfo info{
.sampler = sampler,
};
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
// an attachment, then the sampler for this texture has the same view properties as a
// view for an attachment. Therefore, we can use getAttachmentView to get a
// corresponding VkImageView.
info.imageView = texture->getAttachmentView(range);
} else {
info.imageView = texture->getViewForType(range, expectedType);
}
info.imageLayout = imgutil::getVkLayout(texture->getDefaultLayout());
VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &info,
};
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
set->acquire(texture);
mHistory[set]->write(binding);
mImpl->updateSampler(set, binding, texture, sampler);
}
void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set,
VulkanAttachment attachment) noexcept {
// TOOD: fill-in this region
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) {
return mImpl->bind(setIndex, set, std::move(offsets));
}
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) {
mImpl->commit(commands, pipelineLayout, setMask);
}
void VulkanDescriptorSetManager::createSet(Handle<HwDescriptorSet> handle,
VulkanDescriptorSetLayout* layout) {
auto const vkSet = mDescriptorPool->obtainSet(layout);
auto const& count = layout->count;
auto const vklayout = layout->getVkLayout();
VulkanDescriptorSet* set = mResourceAllocator->construct<VulkanDescriptorSet>(handle,
mResourceAllocator, vkSet, [vkSet, count, vklayout, this](VulkanDescriptorSet* set) {
eraseSetFromHistory(set);
mDescriptorPool->recycle(count, vklayout, vkSet);
});
mHistory[set] = std::make_unique<DescriptorSetHistory>(layout->bitmask.dynamicUbo,
layout->count.dynamicUbo, mResourceAllocator, set);
mImpl->createSet(handle, layout);
}
void VulkanDescriptorSetManager::destroySet(Handle<HwDescriptorSet> handle) {
VulkanDescriptorSet* set = mResourceAllocator->handle_cast<VulkanDescriptorSet*>(handle);
eraseSetFromHistory(set);
}
void VulkanDescriptorSetManager::initVkLayout(VulkanDescriptorSetLayout* layout) {
layout->setVkLayout(mLayoutManager->getVkLayout(layout));
}
void VulkanDescriptorSetManager::eraseSetFromHistory(VulkanDescriptorSet* set) {
DescriptorSetHistory* history = mHistory[set].get();
mHistory.erase(set);
for (uint8_t i = 0; i < mStashedSets.size(); ++i) {
if (mStashedSets[i] == history) {
mStashedSets[i] = nullptr;
}
}
mImpl->destroySet(handle);
}
} // namespace filament::backend
}// namespace filament::backend
@@ -30,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,7 +45,6 @@ public:
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
~VulkanDescriptorSetManager();
void terminate() noexcept;
@@ -71,46 +68,9 @@ public:
void destroySet(Handle<HwDescriptorSet> handle);
void initVkLayout(VulkanDescriptorSetLayout* layout);
private:
class DescriptorSetHistory;
class DescriptorSetLayoutManager;
class DescriptorInfinitePool;
void eraseSetFromHistory(VulkanDescriptorSet* set);
using DescriptorSetHistoryArray =
std::array<DescriptorSetHistory*, UNIQUE_DESCRIPTOR_SET_COUNT>;
struct BoundInfo {
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
DescriptorSetMask setMask;
DescriptorSetHistoryArray boundSets;
bool operator==(BoundInfo const& info) const {
if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) {
return false;
}
bool equal = true;
setMask.forEachSetBit([&](size_t i) {
if (boundSets[i] != info.boundSets[i]) {
equal = false;
}
});
return equal;
}
};
VkDevice mDevice;
VulkanResourceAllocator* mResourceAllocator;
std::unique_ptr<DescriptorSetLayoutManager> mLayoutManager;
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
std::unordered_map<VulkanDescriptorSet*, std::unique_ptr<DescriptorSetHistory>> mHistory;
DescriptorSetHistoryArray mStashedSets = {};
BoundInfo mLastBoundInfo;
class Impl;
Impl* mImpl;
};
}// namespace filament::backend
@@ -740,20 +740,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
// Check the availability of lazily allocated memory
{
context.mLazilyAllocatedMemorySupported = false;
const uint32_t typeCount = context.mMemoryProperties.memoryTypeCount;
for(uint32_t i = 0; i < typeCount; ++i) {
const VkMemoryType type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
}
}
#ifdef NDEBUG
// If we are in release build, we should not have turned on debug extensions
FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported)
@@ -31,22 +31,22 @@ namespace {
std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& context,
VkDevice device, VkExtent2D extent, VkFormat format) {
bool const isDepth = isVkDepthFormat(format);
// Filament expects blit() to work with any texture, so we almost always set these usage flags
// (see copyFrame() and readPixels()).
VkImageUsageFlags const blittable =
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkImageCreateInfo imageInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {extent.width, extent.height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = blittable | (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
// Filament expects blit() to work with any texture, so we almost always set these usage flags.
// TODO: investigate performance implications of setting these flags.
VkImageUsageFlags const blittable
= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkImageCreateInfo imageInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {extent.width, extent.height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = blittable
| (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
};
VkImage image;
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
@@ -220,7 +220,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
.imageArrayLayers = 1,
.imageUsage
= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination (for copyFrame)
| VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination.
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT,// Allows use as a blit source (for readPixels)
// TODO: Setting the preTransform to IDENTITY means we are letting the Android
@@ -239,7 +239,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
};
VkResult result = vkCreateSwapchainKHR(mDevice, &createInfo, VKALLOC, &mSwapchain);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateSwapchainKHR error: " << static_cast<int32_t>(result);
<< "vkGetPhysicalDeviceSurfaceFormatsKHR error: " << static_cast<int32_t>(result);
mSwapChainBundle.colors = enumerate(vkGetSwapchainImagesKHR, mDevice, mSwapchain);
mSwapChainBundle.colorFormat = surfaceFormat.format;
+2 -2
View File
@@ -35,7 +35,7 @@ TEST_F(BackendTest, FrameScheduledCallback) {
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
callable();
callbackCountA++;
}, 0);
});
// Render the first frame.
api.makeCurrent(swapChain, swapChain);
@@ -58,7 +58,7 @@ TEST_F(BackendTest, FrameScheduledCallback) {
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) {
callable();
callbackCountB++;
}, 0);
});
// Render one final frame.
api.makeCurrent(swapChain, swapChain);
-115
View File
@@ -1,115 +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 <gtest/gtest.h>
#include <utils/Allocator.h>
#include <utils/compiler.h>
#include <private/backend/HandleAllocator.h>
#include "utils/Panic.h"
using namespace filament::backend;
// FIXME: consider making this constant non-private so we can use it in tests.
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u;
static constexpr size_t POOL_SIZE_BYTES = 8 * 1024U * 1024U;
// NOTE: actual count may be lower due to alignment requirements
constexpr size_t const POOL_HANDLE_COUNT = POOL_SIZE_BYTES / (32 + 96 + 136); // 31775
// This must match HandleAllocatorGL, so its implementation is present on all platforms.
#define HandleAllocatorTest HandleAllocator<32, 96, 136> // ~4520 / pool / MiB
struct MyHandle {
};
struct Concrete : public MyHandle {
uint8_t data[32];
};
#if GTEST_HAS_EXCEPTIONS
#define EXPECT_THROW_IF_ENABLED EXPECT_THROW
#else
#define EXPECT_THROW_IF_ENABLED(args...)
#endif
TEST(HandlesTest, useAfterFreePool) {
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
allocator.deallocate(handleA);
Handle<MyHandle> handleB = allocator.allocate<Concrete>();
EXPECT_THROW_IF_ENABLED({
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(handleA);
}, utils::PostconditionPanic);
}
TEST(HandlesTest, useAfterFreeHeap) {
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
// Use up all the non-heap handles.
for (int i = 0; i < POOL_HANDLE_COUNT; i++) {
Handle<MyHandle> handle = allocator.allocate<Concrete>();
}
// This one is guaranteed to be a heap handle.
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
EXPECT_TRUE(handleA.getId() & HANDLE_HEAP_FLAG);
allocator.deallocate(handleA);
Handle<MyHandle> handleB = allocator.allocate<Concrete>();
// This should assert:
EXPECT_THROW_IF_ENABLED({
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(handleA);
}, utils::PostconditionPanic);
// simulate a "corrupted" handle
Handle<MyHandle> corruptedHandle { HANDLE_HEAP_FLAG | 0x123456 };
EXPECT_THROW_IF_ENABLED({
Concrete* actualHandleA = allocator.handle_cast<Concrete*>(corruptedHandle);
}, utils::PostconditionPanic);
}
TEST(HandlesTest, isValid) {
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false);
Handle<MyHandle> poolHandle = allocator.allocate<Concrete>();
EXPECT_TRUE((poolHandle.getId() & HANDLE_HEAP_FLAG) == 0u);
// Use up all the non-heap handles.
for (int i = 0; i < POOL_HANDLE_COUNT; i++) {
Handle<MyHandle> handle = allocator.allocate<Concrete>();
}
// This one is guaranteed to be a heap handle.
Handle<MyHandle> heapHandle = allocator.allocate<Concrete>();
EXPECT_TRUE(heapHandle.getId() & HANDLE_HEAP_FLAG);
EXPECT_TRUE(allocator.is_valid(poolHandle));
EXPECT_TRUE(allocator.is_valid(heapHandle));
{
// null handles are invalid
Handle<MyHandle> handle;
EXPECT_FALSE(allocator.is_valid(handle));
}
{
Handle<MyHandle> handle { HANDLE_HEAP_FLAG | 0x123456 };
EXPECT_FALSE(allocator.is_valid(handle));
}
}
+11 -7
View File
@@ -288,6 +288,16 @@ public:
*/
uint32_t jobSystemThreadCount = 0;
/*
* Number of most-recently destroyed textures to track for use-after-free.
*
* This will cause the backend to throw an exception when a texture is freed but still bound
* to a SamplerGroup and used in a draw call. 0 disables completely.
*
* Currently only respected by the Metal backend.
*/
size_t textureUseAfterFreePoolSize = 0;
/**
* When uploading vertex or index data, the Filament Metal backend copies data
* into a shared staging area before transferring it to the GPU. This setting controls
@@ -380,15 +390,9 @@ public:
* it's a GLES2 context. Ignored on other backends.
*/
bool forceGLES2Context = false;
/**
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
* This is only supported for:
* - PlatformEGLAndroid
*/
bool assertNativeWindowIsValid = false;
};
#if UTILS_HAS_THREADING
using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token);
#endif
+2 -20
View File
@@ -118,7 +118,7 @@ public:
Builder& mipLevel(AttachmentPoint attachment, uint8_t level) noexcept;
/**
* Sets the face for cubemap textures at the given attachment point.
* Sets the cubemap face for a given attachment point.
*
* @param attachment The attachment point.
* @param face The associated cubemap face.
@@ -127,12 +127,7 @@ public:
Builder& face(AttachmentPoint attachment, CubemapFace face) noexcept;
/**
* Sets an index of a single layer for 2d array, cubemap array, and 3d textures at the given
* attachment point.
*
* For cubemap array textures, layer is translated into an array index and face according to
* - index: layer / 6
* - face: layer % 6
* Sets the layer for a given attachment point (for 3D textures).
*
* @param attachment The attachment point.
* @param layer The associated cubemap layer.
@@ -140,19 +135,6 @@ public:
*/
Builder& layer(AttachmentPoint attachment, uint32_t layer) noexcept;
/**
* Sets the starting index of the 2d array textures for multiview at the given attachment
* point.
*
* This requires COLOR and DEPTH attachments (if set) to be of 2D array textures.
*
* @param attachment The attachment point.
* @param layerCount The number of layers used for multiview, starting from baseLayer.
* @param baseLayer The starting index of the 2d array texture.
* @return A reference to this Builder for chaining calls.
*/
Builder& multiview(AttachmentPoint attachment, uint8_t layerCount, uint8_t baseLayer = 0) noexcept;
/**
* Creates the RenderTarget object and returns a pointer to it.
*
+1 -4
View File
@@ -508,7 +508,7 @@ public:
*
* Framebuffer as seen on User buffer (PixelBufferDescriptor&)
* screen
*
*
* +--------------------+
* | | .stride .alignment
* | | ----------------------->-->
@@ -538,9 +538,6 @@ public:
* uploaded to it via setImage, the data returned from readPixels will be y-flipped with respect
* to the setImage call.
*
* Note: the texture that backs the COLOR attachment for `renderTarget` must have
* TextureUsage::BLIT_SRC as part of its usage.
*
* @remark
* readPixels() is intended for debugging and testing. It will impact performance significantly.
*
+1 -14
View File
@@ -254,19 +254,6 @@ public:
void* UTILS_NULLABLE getNativeWindow() const noexcept;
/**
* If this flag is passed to setFrameScheduledCallback, then the behavior of the default
* CallbackHandler (when nullptr is passed as the handler argument) is altered to call the
* callback on the Metal completion handler thread (as opposed to the main Filament thread).
* This flag also instructs the Metal backend to release the associated CAMetalDrawable on the
* completion handler thread.
*
* This flag has no effect if a custom CallbackHandler is passed.
*
* @see setFrameScheduledCallback
*/
static constexpr uint64_t CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER = 1;
/**
* FrameScheduledCallback is a callback function that notifies an application when Filament has
* completed processing a frame and that frame is ready to be scheduled for presentation.
@@ -311,7 +298,7 @@ public:
* @see PresentCallable
*/
void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
FrameScheduledCallback&& callback = {}, uint64_t flags = 0);
FrameScheduledCallback&& callback = {});
/**
* Returns whether or not this SwapChain currently has a FrameScheduledCallback set.
+1 -6
View File
@@ -56,12 +56,7 @@ class Bimap {
}
};
using ForwardMap = tsl::robin_map<
KeyDelegate, Value,
KeyHasherDelegate,
std::equal_to<KeyDelegate>,
std::allocator<std::pair<KeyDelegate, Value>>,
true>;
using ForwardMap = tsl::robin_map<KeyDelegate, Value, KeyHasherDelegate>;
using BackwardMap = tsl::robin_map<Value, KeyDelegate, ValueHash>;
Allocator mAllocator;
@@ -1,103 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "HwDescriptorSetLayoutFactory.h"
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <private/backend/DriverApi.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Hash.h>
#include <utils/Log.h>
#include <algorithm>
#include <utility>
#include <stdint.h>
#include <stdlib.h>
namespace filament {
using namespace utils;
using namespace backend;
size_t HwDescriptorSetLayoutFactory::Parameters::hash() const noexcept {
return utils::hash::murmurSlow(
reinterpret_cast<uint8_t const *>(dsl.bindings.data()),
dsl.bindings.size() * sizeof(backend::DescriptorSetLayoutBinding),
42);
}
bool operator==(HwDescriptorSetLayoutFactory::Parameters const& lhs,
HwDescriptorSetLayoutFactory::Parameters const& rhs) noexcept {
return (lhs.dsl.bindings.size() == rhs.dsl.bindings.size()) &&
std::equal(
lhs.dsl.bindings.begin(), lhs.dsl.bindings.end(),
rhs.dsl.bindings.begin());
}
// ------------------------------------------------------------------------------------------------
HwDescriptorSetLayoutFactory::HwDescriptorSetLayoutFactory()
: mArena("HwDescriptorSetLayoutFactory::mArena", SET_ARENA_SIZE),
mBimap(mArena) {
mBimap.reserve(256);
}
HwDescriptorSetLayoutFactory::~HwDescriptorSetLayoutFactory() noexcept = default;
void HwDescriptorSetLayoutFactory::terminate(DriverApi&) noexcept {
assert_invariant(mBimap.empty());
}
auto HwDescriptorSetLayoutFactory::create(DriverApi& driver,
backend::DescriptorSetLayout dsl) noexcept -> Handle {
std::sort(dsl.bindings.begin(), dsl.bindings.end(),
[](auto&& lhs, auto&& rhs) {
return lhs.binding < rhs.binding;
});
// see if we already have seen this RenderPrimitive
Key const key({ dsl });
auto pos = mBimap.find(key);
// the common case is that we've never seen it (i.e.: no reuse)
if (UTILS_LIKELY(pos == mBimap.end())) {
auto handle = driver.createDescriptorSetLayout(std::move(dsl));
mBimap.insert(key, { handle });
return handle;
}
++(pos->first.pKey->refs);
return pos->second.handle;
}
void HwDescriptorSetLayoutFactory::destroy(DriverApi& driver, Handle handle) noexcept {
// look for this handle in our map
auto pos = mBimap.find(Value{ handle });
if (--pos->second.pKey->refs == 0) {
mBimap.erase(pos);
driver.destroyDescriptorSetLayout(handle);
}
}
} // namespace filament
-119
View File
@@ -1,119 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H
#define TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H
#include "Bimap.h"
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/Allocator.h>
#include <functional>
#include <stddef.h>
#include <stdint.h>
namespace filament {
class FEngine;
class HwDescriptorSetLayoutFactory {
public:
using Handle = backend::DescriptorSetLayoutHandle;
HwDescriptorSetLayoutFactory();
~HwDescriptorSetLayoutFactory() noexcept;
HwDescriptorSetLayoutFactory(HwDescriptorSetLayoutFactory const& rhs) = delete;
HwDescriptorSetLayoutFactory(HwDescriptorSetLayoutFactory&& rhs) noexcept = delete;
HwDescriptorSetLayoutFactory& operator=(HwDescriptorSetLayoutFactory const& rhs) = delete;
HwDescriptorSetLayoutFactory& operator=(HwDescriptorSetLayoutFactory&& rhs) noexcept = delete;
void terminate(backend::DriverApi& driver) noexcept;
struct Parameters { // 16 bytes + heap allocations
backend::DescriptorSetLayout dsl;
size_t hash() const noexcept;
};
friend bool operator==(Parameters const& lhs, Parameters const& rhs) noexcept;
Handle create(backend::DriverApi& driver, backend::DescriptorSetLayout dsl) noexcept;
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
private:
struct Key { // 24 bytes
// The key should not be copyable, unfortunately due to how the Bimap works we have
// to copy-construct it once.
Key(Key const&) = default;
Key& operator=(Key const&) = delete;
Key& operator=(Key&&) noexcept = delete;
explicit Key(Parameters const& params) : params(params), refs(1) { }
Parameters params;
mutable uint32_t refs; // 4 bytes
bool operator==(Key const& rhs) const noexcept {
return params == rhs.params;
}
};
struct KeyHasher {
size_t operator()(Key const& p) const noexcept {
return p.params.hash();
}
};
struct Value { // 4 bytes
Handle handle;
};
struct ValueHasher {
size_t operator()(Value const v) const noexcept {
return std::hash<Handle::HandleId>()(v.handle.getId());
}
};
friend bool operator==(Value const lhs, Value const rhs) noexcept {
return lhs.handle == rhs.handle;
}
// Size of the arena used for the "set" part of the bimap
// about ~1K entries before fall back to heap
static constexpr size_t SET_ARENA_SIZE = 24 * 1024;
// Arena for the set<>, using a pool allocator inside a heap area.
using PoolAllocatorArena = utils::Arena<
utils::PoolAllocatorWithFallback<sizeof(Key)>,
utils::LockingPolicy::NoLock,
utils::TrackingPolicy::Untracked,
utils::AreaPolicy::HeapArea>;
// Arena where the set memory is allocated
PoolAllocatorArena mArena;
// The special Bimap
Bimap<Key, Value, KeyHasher, ValueHasher,
utils::STLAllocator<Key, PoolAllocatorArena>> mBimap;
};
} // namespace filament
#endif // TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H
+3 -4
View File
@@ -48,7 +48,7 @@ public:
void terminate(backend::DriverApi& driver) noexcept;
struct Parameters { // 12 bytes
struct Parameters { // 20 bytes
backend::VertexBufferHandle vbh; // 4
backend::IndexBufferHandle ibh; // 4
backend::PrimitiveType type; // 4
@@ -65,7 +65,7 @@ public:
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
private:
struct Key { // 16 bytes
struct Key {
// The key should not be copyable, unfortunately due to how the Bimap works we have
// to copy-construct it once.
Key(Key const&) = default;
@@ -100,8 +100,7 @@ private:
}
// Size of the arena used for the "set" part of the bimap
// about ~65K entry before fall back to heap
static constexpr size_t SET_ARENA_SIZE = 1 * 1024 * 1024;
static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024;
// Arena for the set<>, using a pool allocator inside a heap area.
using PoolAllocatorArena = utils::Arena<
+3 -4
View File
@@ -48,7 +48,7 @@ public:
void terminate(backend::DriverApi& driver) noexcept;
struct Parameters { // 132 bytes
struct Parameters { // 136 bytes
uint8_t bufferCount;
uint8_t attributeCount;
uint8_t padding[2] = {};
@@ -66,7 +66,7 @@ public:
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
private:
struct Key { // 136 bytes
struct Key { // 140 bytes
// The key should not be copyable, unfortunately due to how the Bimap works we have
// to copy-construct it once.
Key(Key const&) = default;
@@ -101,8 +101,7 @@ private:
}
// Size of the arena used for the "set" part of the bimap
// about ~15K entry before fall back to heap
static constexpr size_t SET_ARENA_SIZE = 2 * 1024 * 1024;
static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024;
// Arena for the set<>, using a pool allocator inside a heap area.
using PoolAllocatorArena = utils::Arena<
+6 -13
View File
@@ -250,16 +250,6 @@ PostProcessManager::PostProcessManager(FEngine& engine) noexcept
PostProcessManager::~PostProcessManager() noexcept = default;
void PostProcessManager::setFrameUniforms(backend::DriverApi& driver,
TypedUniformBuffer<PerViewUib>& uniforms) noexcept {
mPostProcessDescriptorSet.setFrameUniforms(driver, uniforms);
mSsrPassDescriptorSet.setFrameUniforms(uniforms);
}
void PostProcessManager::bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept {
mPostProcessDescriptorSet.bind(driver);
}
UTILS_NOINLINE
void PostProcessManager::registerPostProcessMaterial(std::string_view name,
MaterialInfo const& info) {
@@ -391,7 +381,7 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept {
++first;
}
mPostProcessDescriptorSet.terminate(engine.getDescriptorSetLayoutFactory(), driver);
mPostProcessDescriptorSet.terminate(driver);
mSsrPassDescriptorSet.terminate(driver);
}
@@ -622,6 +612,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
userViewMatrix = cameraInfo.getUserViewMatrix(), uvFromClipMatrix, historyProjection,
options, passBuilder = passBuilder]
(FrameGraphResources const& resources, auto const& data, DriverApi& driver) mutable {
// set our frame uniforms in our descriptor-set
assert_invariant(mFrameUniforms);
mSsrPassDescriptorSet.setFrameUniforms(*mFrameUniforms);
// set structure sampler
mSsrPassDescriptorSet.prepareStructure(data.structure ?
resources.getTexture(data.structure) : getOneTexture());
@@ -643,7 +637,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
auto out = resources.getRenderPassInfo();
// Remove the HAS_SHADOWING RenderFlags, since it's irrelevant when rendering reflections
passBuilder.renderFlags(RenderPass::HAS_SHADOWING, 0);
passBuilder.renderFlags(~RenderPass::HAS_SHADOWING, 0);
// use our special SSR variant, it can only be applied to object that have
// the SCREEN_SPACE ReflectionMode.
@@ -3410,7 +3404,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height
});
mi->commit(driver);
mi->use(driver);
auto pipeline = material.getPipelineState(mEngine);
+12 -4
View File
@@ -103,6 +103,14 @@ public:
void init() noexcept;
void terminate(backend::DriverApi& driver) noexcept;
void bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept {
assert_invariant(mFrameUniforms);
if (mFrameUniforms) {
mPostProcessDescriptorSet.setFrameUniforms(driver, *mFrameUniforms);
mPostProcessDescriptorSet.bind(driver);
}
}
void configureTemporalAntiAliasingMaterial(
TemporalAntiAliasingOptions const& taaOptions) noexcept;
@@ -373,16 +381,16 @@ public:
render(out, combo.first, combo.second, driver);
}
void setFrameUniforms(backend::DriverApi& driver,
TypedUniformBuffer<PerViewUib>& uniforms) noexcept;
void bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept;
void setFrameUniforms(TypedUniformBuffer<PerViewUib>& uniforms) noexcept {
mFrameUniforms = std::addressof(uniforms);
}
private:
FEngine& mEngine;
mutable SsrPassDescriptorSet mSsrPassDescriptorSet;
mutable PostProcessDescriptorSet mPostProcessDescriptorSet;
TypedUniformBuffer<PerViewUib>* mFrameUniforms = nullptr;
struct BilateralPassConfig {
uint8_t kernelSize = 11;
+20 -35
View File
@@ -655,18 +655,12 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
bool const hasSkinning = soaVisibility[i].skinning;
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
// if we are already an SSR variant, the SRE bit is already set
// if we are already an SSR variant, the SRE bit is already set,
// there is no harm setting it again
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
Variant renderableVariant{ variant };
// we can't have SSR and shadowing together by construction
bool const isSsrVariant = Variant::isSSRVariant(variant);
assert_invariant((isSsrVariant && !hasShadowing) || !isSsrVariant);
if (!isSsrVariant) {
// set the SRE variant, unless we're in SSR mode
renderableVariant.setShadowReceiver(soaVisibility[i].receiveShadows && hasShadowing);
}
Variant renderableVariant = variant;
renderableVariant.setShadowReceiver(
Variant::isSSRVariant(variant) || (soaVisibility[i].receiveShadows & hasShadowing));
renderableVariant.setSkinning(hasSkinningOrMorphing);
FRenderableManager::SkinningBindingInfo const& skinning = soaSkinning[i];
@@ -935,17 +929,15 @@ void RenderPass::Executor::execute(FEngine& engine,
// Maximum space occupied in the CircularBuffer by a single `Command`. This must be
// reevaluated when the inner loop below adds DriverApi commands or when we change the
// CommandStream protocol. Currently, the maximum is 248 bytes.
// CommandStream protocol. Currently, the maximum is 320 bytes.
// The batch size is calculated by adding the size of all commands that can possibly be
// emitted per draw call:
constexpr size_t const maxCommandSizeInBytes =
sizeof(CustomCommand) +
sizeof(COMMAND_TYPE(scissor)) +
sizeof(COMMAND_TYPE(bindDescriptorSet)) +
sizeof(COMMAND_TYPE(bindDescriptorSet)) +
sizeof(COMMAND_TYPE(bindPipeline)) +
sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 8) +
sizeof(COMMAND_TYPE(setPushConstant)) +
sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
sizeof(COMMAND_TYPE(draw2));
@@ -1025,25 +1017,18 @@ void RenderPass::Executor::execute(FEngine& engine,
pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] =
ma->getDescriptorSetLayout().getHandle();
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
// It is possible to get a post-process material here (even though it's
// not technically a public API yet, it is used by the IBLPrefilterLibrary.
// Ideally we would have a more formal compute API). In this case, we need
// to set the post-process descriptor-set.
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
} else {
// If we have a ColorPassDescriptorSet we use it to bind the per-view
// descriptor-set (ideally only if it changed). If we don't, it means
// the descriptor-set is already bound and the layout we got from the
// material above should match. This is the case for situations where we
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and
// structure passes.
if (mColorPassDescriptorSet) {
// We have a ColorPassDescriptorSet, we need to go through it for binding
// the per-view descriptor-set because its layout can change based on the
// material.
mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex());
}
// If we have a ColorPassDescriptorSet we use it to bind the per-view
// descriptor-set (ideally only if it changed). If we don't, it means
// the descriptor-set is already bound and the layout we got from the
// material above should match. This is the case for situations where we
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and
// structure passes.
if (mColorPassDescriptorSet) {
// We have a ColorPassDescriptorSet, we need to go through it for binding
// the per-view descriptor-set because its layout can change based on the
// material.
mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex());
}
// Each MaterialInstance has its own descriptor set. This binds it.
+34 -48
View File
@@ -261,15 +261,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
auto& passList = data.passList;
// Directional, cascaded shadow maps
// if the view has shadowing enabled, the SRE variant could be used, so we must
// generate the shadow maps
if (view.needsDirectionalShadowMaps()) {
auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters();
auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters();
if (!directionalShadowCastersRange.empty()) {
for (auto& shadowMap : getCascadedShadowMap()) {
// For the directional light, we already know if it has visible shadows.
// if hasVisibleShadows() is false, we're guaranteed the shader won't
// sample this shadow map (see PerViewUib::cascades and
// ShadowMapManager::updateCascadeShadowMaps)
// for the directional light, we already know if it has visible shadows.
if (shadowMap.hasVisibleShadows()) {
passList.push_back({
{}, &shadowMap, directionalShadowCastersRange,
@@ -279,10 +274,8 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
}
// Point lights and Spotlight shadow maps
// if the view has shadowing enabled, the SRE variant could be used, so we must
// generate the shadow maps
if (view.needsPointShadowMaps()) {
auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters();
auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters();
if (!spotShadowCastersRange.empty()) {
for (auto& shadowMap : getSpotShadowMaps()) {
assert_invariant(!shadowMap.isDirectionalShadow());
@@ -300,11 +293,11 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
break;
}
// For spot/point lights, shadowMap.hasVisibleShadows() doesn't guarantee
// the shader won't access the shadow map, so we must generate it.
passList.push_back({
{}, &shadowMap, spotShadowCastersRange,
VISIBLE_DYN_SHADOW_RENDERABLE });
if (shadowMap.hasVisibleShadows()) {
passList.push_back({
{}, &shadowMap, spotShadowCastersRange,
VISIBLE_DYN_SHADOW_RENDERABLE });
}
}
}
@@ -318,6 +311,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
scene, mainCameraInfo, userTime, passBuilder = passBuilder](
FrameGraphResources const&, auto const& data, DriverApi& driver) mutable {
// Note: we could almost parallel_for the loop below, the problem currently is
// that updatePrimitivesLod() updates temporary global state.
// prepareSpotShadowMap() also update the visibility of renderable. These two
@@ -327,6 +321,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// Generate a RenderPass for each shadow map
for (auto const& entry : data.passList) {
ShadowMap const& shadowMap = *entry.shadowMap;
assert_invariant(shadowMap.hasVisibleShadows());
// Note: this loop can generate a lot of commands that come out of the
// "per frame command arena". The allocation persists until the
@@ -344,18 +339,14 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// we should never be here
break;
case ShadowType::SPOT:
if (shadowMap.hasVisibleShadows()) {
ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
}
ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
break;
case ShadowType::POINT:
if (shadowMap.hasVisibleShadows()) {
ShadowMapManager::cullPointShadowMap(shadowMap, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
}
ShadowMapManager::cullPointShadowMap(shadowMap, view,
scene->getRenderableData(), entry.range,
scene->getLightData());
break;
}
@@ -429,21 +420,22 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
auto const& passList = prepareShadowPass.getData().passList;
for (auto const& entry: passList) {
assert_invariant(entry.shadowMap->hasVisibleShadows());
const uint8_t layer = entry.shadowMap->getLayer();
const auto* options = entry.shadowMap->getShadowOptions();
const auto msaaSamples = textureRequirements.msaaSamples;
const bool blur = entry.shadowMap->hasVisibleShadows() &&
view.hasVSM() && options->vsm.blurWidth > 0.0f;
auto& shadowPass = fg.addPass<ShadowPassData>("Shadow Pass",
[&](FrameGraph::Builder& builder, auto& data) {
const bool blur = view.hasVSM() && options->vsm.blurWidth > 0.0f;
FrameGraphRenderPass::Descriptor renderTargetDesc{};
data.output = builder.createSubresource(prepareShadowPass->shadows,
"Shadowmap Layer", { .layer = layer });
if (UTILS_UNLIKELY(view.hasVSM())) {
if (view.hasVSM()) {
// Each shadow pass has its own sample count, but textures are created with
// a default count of 1 because we're using "magic resolve" (sample count is
// set on the render target).
@@ -455,6 +447,13 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
.format = TextureFormat::DEPTH16,
});
// Temporary (resolved) texture used to render the shadowmap when blurring
// is needed -- it'll be used as the source of the blur.
data.tempBlurSrc = builder.createTexture("Temporary Shadowmap", {
.width = textureRequirements.size, .height = textureRequirements.size,
.format = textureRequirements.format
});
depth = builder.write(depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
@@ -469,15 +468,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
renderTargetDesc.clearColor = vsmClearColor;
renderTargetDesc.samples = msaaSamples;
if (UTILS_UNLIKELY(blur)) {
// Temporary (resolved) texture used to render the shadowmap when blurring
// is needed -- it'll be used as the source of the blur.
data.tempBlurSrc = builder.createTexture("Temporary Shadowmap", {
.width = textureRequirements.size,
.height = textureRequirements.size,
.format = textureRequirements.format
});
if (blur) {
data.tempBlurSrc = builder.write(data.tempBlurSrc,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
@@ -519,20 +510,15 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
auto rt = resources.getRenderPassInfo(data.rt);
driver.beginRenderPass(rt.target, rt.params);
// if we know there are no visible shadows, we can skip rendering, but
// we need the render-pass to clear/initialize the shadow-map
// Note: this is always true for directional/cascade shadows.
if (entry.shadowMap->hasVisibleShadows()) {
entry.shadowMap->bind(driver);
entry.executor.overrideScissor(entry.shadowMap->getScissor());
entry.executor.execute(engine, "Shadow Pass");
}
entry.shadowMap->bind(driver);
entry.executor.overrideScissor(entry.shadowMap->getScissor());
entry.executor.execute(engine, "Shadow Pass");
driver.endRenderPass();
});
// now emit the blurring passes if needed
if (UTILS_UNLIKELY(blur)) {
if (view.hasVSM()) {
auto& ppm = engine.getPostProcessManager();
const float blurWidth = options->vsm.blurWidth;
+2 -2
View File
@@ -29,8 +29,8 @@ void* SwapChain::getNativeWindow() const noexcept {
}
void SwapChain::setFrameScheduledCallback(
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
downcast(this)->setFrameScheduledCallback(handler, std::move(callback), flags);
backend::CallbackHandler* handler, FrameScheduledCallback&& callback) {
downcast(this)->setFrameScheduledCallback(handler, std::move(callback));
}
bool SwapChain::isFrameScheduledCallbackSet() const noexcept {
-75
View File
@@ -1,75 +0,0 @@
/*
* Copyright (C) 2021 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_TYPEDBUFFER_H
#define TNT_FILAMENT_TYPEDBUFFER_H
#include <private/backend/DriverApi.h>
#include <backend/BufferDescriptor.h>
#include <stddef.h>
#include <string.h>
namespace filament {
template<typename T, size_t N = 1>
class TypedBuffer { // NOLINT(cppcoreguidelines-pro-type-member-init)
public:
T& itemAt(size_t i) noexcept {
mSomethingDirty = true;
return mBuffer[i];
}
T& edit() noexcept {
return itemAt(0);
}
// size of the uniform buffer in bytes
size_t getSize() const noexcept { return sizeof(T) * N; }
// return if any uniform has been changed
bool isDirty() const noexcept { return mSomethingDirty; }
// mark the whole buffer as "clean" (no modified uniforms)
void clean() const noexcept { mSomethingDirty = false; }
// helper functions
backend::BufferDescriptor toBufferDescriptor(backend::DriverApi& driver) const noexcept {
return toBufferDescriptor(driver, 0, getSize());
}
// copy the UBO data and cleans the dirty bits
backend::BufferDescriptor toBufferDescriptor(
backend::DriverApi& driver, size_t offset, size_t size) const noexcept {
backend::BufferDescriptor p;
p.size = size;
p.buffer = driver.allocate(p.size); // TODO: use out-of-line buffer if too large
memcpy(p.buffer, reinterpret_cast<const char*>(mBuffer) + offset, p.size); // inlined
clean();
return p;
}
private:
T mBuffer[N];
mutable bool mSomethingDirty = false;
};
} // namespace filament
#endif // TNT_FILAMENT_TYPEDBUFFER_H
+20 -33
View File
@@ -103,12 +103,12 @@ Engine* FEngine::create(Engine::Builder const& builder) {
}
DriverConfig const driverConfig{
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.textureUseAfterFreePoolSize = instance->getConfig().textureUseAfterFreePoolSize,
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile,
.disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck,
.forceGLES2Context = instance->getConfig().forceGLES2Context,
.stereoscopicType = instance->getConfig().stereoscopicType,
.assertNativeWindowIsValid = instance->getConfig().assertNativeWindowIsValid,
.stereoscopicType = instance->getConfig().stereoscopicType,
};
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
@@ -345,17 +345,14 @@ void FEngine::init() {
mPerViewDescriptorSetLayoutSsrVariant = {
mHwDescriptorSetLayoutFactory,
driverApi,
descriptor_sets::getSsrVariantLayout() };
mPerViewDescriptorSetLayoutDepthVariant = {
mHwDescriptorSetLayoutFactory,
driverApi,
descriptor_sets::getDepthVariantLayout() };
mPerRenderableDescriptorSetLayout = {
mHwDescriptorSetLayoutFactory,
driverApi,
descriptor_sets::getPerRenderableLayout() };
@@ -486,9 +483,9 @@ void FEngine::shutdown() {
mLightManager.terminate(); // free-up all lights
mCameraManager.terminate(*this); // free-up all cameras
mPerViewDescriptorSetLayoutDepthVariant.terminate(mHwDescriptorSetLayoutFactory, driver);
mPerViewDescriptorSetLayoutSsrVariant.terminate(mHwDescriptorSetLayoutFactory, driver);
mPerRenderableDescriptorSetLayout.terminate(mHwDescriptorSetLayoutFactory, driver);
mPerViewDescriptorSetLayoutDepthVariant.terminate(driver);
mPerViewDescriptorSetLayoutSsrVariant.terminate(driver);
mPerRenderableDescriptorSetLayout.terminate(driver);
driver.destroyRenderPrimitive(mFullScreenTriangleRph);
@@ -589,10 +586,12 @@ void FEngine::prepare() {
});
}
mMaterials.forEach([](FMaterial* material) {
// Commit default material instances.
mMaterials.forEach([&driver](FMaterial* material) {
#if FILAMENT_ENABLE_MATDBG
material->checkProgramEdits();
#endif
material->getDefaultInstance()->commit(driver);
});
}
@@ -701,12 +700,12 @@ int FEngine::loop() {
DriverConfig const driverConfig {
.handleArenaSize = getRequestedDriverHandleArenaSize(),
.textureUseAfterFreePoolSize = mConfig.textureUseAfterFreePoolSize,
.metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes,
.disableParallelShaderCompile = mConfig.disableParallelShaderCompile,
.disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck,
.forceGLES2Context = mConfig.forceGLES2Context,
.stereoscopicType = mConfig.stereoscopicType,
.assertNativeWindowIsValid = mConfig.assertNativeWindowIsValid,
};
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
@@ -835,15 +834,6 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
return p;
}
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material) noexcept {
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material);
if (UTILS_UNLIKELY(p)) { // should never happen
auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
pos.first->second.insert(p);
}
return p;
}
/*
* Objects created without a Builder
*/
@@ -1098,32 +1088,29 @@ bool FEngine::destroy(const FInstanceBuffer* p){
UTILS_NOINLINE
bool FEngine::destroy(const FMaterial* ptr) {
if (ptr == nullptr) return true;
bool const success = terminateAndDestroy(ptr, mMaterials);
if (success) {
auto pos = mMaterialInstances.find(ptr);
if (pos != mMaterialInstances.cend()) {
// ensure we've destroyed all instances before destroying the material
if (!ASSERT_PRECONDITION_NON_FATAL(pos->second.empty(),
"destroying material \"%s\" but %u instances still alive",
ptr->getName().c_str(), (*pos).second.size())) {
return false;
}
auto pos = mMaterialInstances.find(ptr);
if (pos != mMaterialInstances.cend()) {
// ensure we've destroyed all instances before destroying the material
if (!ASSERT_PRECONDITION_NON_FATAL(pos->second.empty(),
"destroying material \"%s\" but %u instances still alive",
ptr->getName().c_str(), (*pos).second.size())) {
return false;
}
}
return success;
return terminateAndDestroy(ptr, mMaterials);
}
UTILS_NOINLINE
bool FEngine::destroy(const FMaterialInstance* ptr) {
if (ptr == nullptr) return true;
if (ptr->isDefaultInstance()) return false;
auto pos = mMaterialInstances.find(ptr->getMaterial());
assert_invariant(pos != mMaterialInstances.cend());
if (pos != mMaterialInstances.cend()) {
return terminateAndDestroy(ptr, pos->second);
}
// this shouldn't happen, this would be double-free
return false;
// if we don't find this instance's material it might be because it's the default instance
// in which case it fine to ignore.
return true;
}
UTILS_NOINLINE
-9
View File
@@ -23,7 +23,6 @@
#include "DFG.h"
#include "PostProcessManager.h"
#include "ResourceList.h"
#include "HwDescriptorSetLayoutFactory.h"
#include "HwVertexBufferInfoFactory.h"
#include "components/CameraManager.h"
@@ -299,12 +298,9 @@ public:
void createLight(const LightManager::Builder& builder, utils::Entity entity);
FRenderer* createRenderer() noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material,
const FMaterialInstance* other, const char* name) noexcept;
FMaterialInstance* createMaterialInstance(const FMaterial* material) noexcept;
FScene* createScene() noexcept;
FView* createView() noexcept;
FFence* createFence() noexcept;
@@ -451,10 +447,6 @@ public:
return mHwVertexBufferInfoFactory;
}
HwDescriptorSetLayoutFactory& getDescriptorSetLayoutFactory() noexcept {
return mHwDescriptorSetLayoutFactory;
}
DescriptorSetLayout const& getPerViewDescriptorSetLayoutDepthVariant() const noexcept {
return mPerViewDescriptorSetLayoutDepthVariant;
}
@@ -536,7 +528,6 @@ private:
FCameraManager mCameraManager;
std::shared_ptr<ResourceAllocatorDisposer> mResourceAllocatorDisposer;
HwVertexBufferInfoFactory mHwVertexBufferInfoFactory;
HwDescriptorSetLayoutFactory mHwDescriptorSetLayoutFactory;
DescriptorSetLayout mPerViewDescriptorSetLayoutDepthVariant;
DescriptorSetLayout mPerViewDescriptorSetLayoutSsrVariant;
DescriptorSetLayout mPerRenderableDescriptorSetLayout;
+18 -40
View File
@@ -327,6 +327,9 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
processDepthVariants(engine, parser);
processDescriptorSets(engine, parser);
// we can only initialize the default instance once we're initialized ourselves
new(&mDefaultInstanceStorage) FMaterialInstance(engine, this);
mPerViewLayoutIndex = ColorPassDescriptorSet::getIndex(
mIsVariantLit,
mReflectionMode == ReflectionMode::SCREEN_SPACE ||
@@ -343,7 +346,9 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
#endif
}
FMaterial::~FMaterial() noexcept = default;
FMaterial::~FMaterial() noexcept {
std::destroy_at(getDefaultInstance());
}
void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept {
if (mMaterialDomain == MaterialDomain::SURFACE) {
@@ -402,16 +407,10 @@ void FMaterial::terminate(FEngine& engine) {
destroyPrograms(engine);
if (mDefaultMaterialInstance) {
mDefaultMaterialInstance->setDefaultInstance(false);
engine.destroy(mDefaultMaterialInstance);
mDefaultMaterialInstance = nullptr;
}
getDefaultInstance()->terminate(engine);
mPerViewDescriptorSetLayout.terminate(
engine.getDescriptorSetLayoutFactory(), engine.getDriverApi());
mDescriptorSetLayout.terminate(
engine.getDescriptorSetLayoutFactory(), engine.getDriverApi());
mPerViewDescriptorSetLayout.terminate(engine.getDriverApi());
mDescriptorSetLayout.terminate(engine.getDriverApi());
}
void FMaterial::compile(CompilerPriorityQueue priority,
@@ -457,21 +456,7 @@ void FMaterial::compile(CompilerPriorityQueue priority,
}
FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
if (mDefaultMaterialInstance) {
// if we have a default instance, use it to create a new one
return FMaterialInstance::duplicate(mDefaultMaterialInstance, name);
} else {
// but if we don't, just create an instance with all the default parameters
return mEngine.createMaterialInstance(this);
}
}
FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
mDefaultMaterialInstance = mEngine.createMaterialInstance(this);
mDefaultMaterialInstance->setDefaultInstance(true);
}
return mDefaultMaterialInstance;
return FMaterialInstance::duplicate(getDefaultInstance(), name);
}
bool FMaterial::hasParameter(const char* name) const noexcept {
@@ -594,15 +579,12 @@ Program FMaterial::getProgramWithVariants(
Program program;
program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size())
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(mMaterialParser->getShaderLanguage())
.diagnostics(mName,
[this, variant, vertexVariant, fragmentVariant](
io::ostream& out) -> io::ostream& {
return out << mName.c_str_safe() << ", variant=(" << io::hex
<< (int)variant.key << io::dec << "), vertexVariant=(" << io::hex
<< (int)vertexVariant.key << io::dec << "), fragmentVariant=("
<< io::hex << (int)fragmentVariant.key << io::dec << ")";
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(mMaterialParser->getShaderLanguage())
.diagnostics(mName,
[this, variant](io::ostream& out) -> io::ostream& {
return out << mName.c_str_safe()
<< ", variant=(" << io::hex << variant.key << io::dec << ")";
});
if (UTILS_UNLIKELY(mMaterialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
@@ -1057,13 +1039,9 @@ void FMaterial::processDescriptorSets(FEngine& engine, MaterialParser const* con
success = parser->getDescriptorSetLayout(&descriptorSetLayout);
assert_invariant(success);
mDescriptorSetLayout = {
engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), std::move(descriptorSetLayout[0]) };
mDescriptorSetLayout = { engine.getDriverApi(), std::move(descriptorSetLayout[0]) };
mPerViewDescriptorSetLayout = {
engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), std::move(descriptorSetLayout[1]) };
mPerViewDescriptorSetLayout = { engine.getDriverApi(), std::move(descriptorSetLayout[1]) };
}
backend::descriptor_binding_t FMaterial::getSamplerBinding(
+5 -2
View File
@@ -127,7 +127,9 @@ public:
return const_cast<FMaterial*>(this)->getDefaultInstance();
}
FMaterialInstance* getDefaultInstance() noexcept;
FMaterialInstance* getDefaultInstance() noexcept {
return std::launder(reinterpret_cast<FMaterialInstance*>(&mDefaultInstanceStorage));
}
FEngine& getEngine() const noexcept { return mEngine; }
@@ -326,7 +328,8 @@ private:
bool mSpecularAntiAliasing = false;
// reserve some space to construct the default material instance
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
std::aligned_storage<sizeof(FMaterialInstance), alignof(FMaterialInstance)>::type mDefaultInstanceStorage;
static_assert(sizeof(mDefaultInstanceStorage) >= sizeof(FMaterialInstance));
SamplerInterfaceBlock mSamplerInterfaceBlock;
BufferInterfaceBlock mUniformInterfaceBlock;
+20 -32
View File
@@ -47,7 +47,6 @@
#include <cmath>
#include <mutex>
#include <string_view>
#include <utility>
using namespace filament::math;
using namespace utils;
@@ -65,7 +64,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material)
mDepthWrite(false),
mHasScissor(false),
mIsDoubleSided(false),
mIsDefaultInstance(false),
mTransparencyMode(TransparencyMode::DEFAULT) {
FEngine::DriverApi& driver = engine.getDriverApi();
@@ -129,7 +127,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mDepthWrite(other->mDepthWrite),
mHasScissor(false),
mIsDoubleSided(other->mIsDoubleSided),
mIsDefaultInstance(false),
mScissorRect(other->mScissorRect),
mName(name ? CString(name) : other->mName) {
@@ -202,10 +199,6 @@ void FMaterialInstance::commit(DriverApi& driver) const {
mDescriptorSet.setSampler(binding, handle, p.params);
}
}
// TODO: eventually we should remove this in RELEASE builds
fixMissingSamplers();
// Commit descriptors if needed (e.g. when textures are updated,or the first time)
mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver);
}
@@ -325,13 +318,26 @@ const char* FMaterialInstance::getName() const noexcept {
void FMaterialInstance::use(FEngine::DriverApi& driver) const {
if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) {
std::call_once(mMissingSamplersFlag, [this]() {
auto const& list = mMaterial->getSamplerInterfaceBlock().getSamplerInfoList();
// Here we check that all declared sampler parameters are set, this is required by
// Vulkan and Metal; GL is more permissive. If a sampler parameter is not set, we will
// log a warning once per MaterialInstance in the system log and patch-in a dummy
// texture.
auto const& layout = mMaterial->getDescriptorSetLayout();
auto const samplersDescriptors = layout.getSamplerDescriptors();
auto const validDescriptors = mDescriptorSet.getValidDescriptors();
auto const missingSamplerDescriptors =
(validDescriptors & samplersDescriptors) ^ samplersDescriptors;
if (UTILS_UNLIKELY(missingSamplerDescriptors.any())) {
auto const& list = mMaterial->getSamplerInterfaceBlock().getSamplerInfoList();
std::call_once(mMissingSamplersFlag, [this, missingSamplerDescriptors, &list]() {
slog.w << "sampler parameters not set in MaterialInstance \""
<< mName.c_str_safe() << "\" or Material \""
<< mMaterial->getName().c_str_safe() << "\":\n";
mMissingSamplerDescriptors.forEachSetBit([&list](descriptor_binding_t binding) {
missingSamplerDescriptors.forEachSetBit([&list](descriptor_binding_t binding) {
auto pos = std::find_if(list.begin(), list.end(), [binding](const auto& item) {
return item.binding == binding;
});
@@ -342,29 +348,8 @@ void FMaterialInstance::use(FEngine::DriverApi& driver) const {
});
flush(slog.w);
});
mMissingSamplerDescriptors.clear();
}
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
}
void FMaterialInstance::fixMissingSamplers() const {
// Here we check that all declared sampler parameters are set, this is required by
// Vulkan and Metal; GL is more permissive. If a sampler parameter is not set, we will
// log a warning once per MaterialInstance in the system log and patch-in a dummy
// texture.
auto const& layout = mMaterial->getDescriptorSetLayout();
auto const samplersDescriptors = layout.getSamplerDescriptors();
auto const validDescriptors = mDescriptorSet.getValidDescriptors();
auto const missingSamplerDescriptors =
(validDescriptors & samplersDescriptors) ^ samplersDescriptors;
// always record the missing samplers state at commit() time
mMissingSamplerDescriptors = missingSamplerDescriptors;
if (UTILS_UNLIKELY(missingSamplerDescriptors.any())) {
// here we need to set the samplers that are missing
auto const& list = mMaterial->getSamplerInterfaceBlock().getSamplerInfoList();
missingSamplerDescriptors.forEachSetBit([this, &list](descriptor_binding_t binding) {
auto pos = std::find_if(list.begin(), list.end(), [binding](const auto& item) {
return item.binding == binding;
@@ -395,7 +380,10 @@ void FMaterialInstance::fixMissingSamplers() const {
}
}
});
mDescriptorSet.commit(layout, driver);
}
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
}
} // namespace filament
+3 -15
View File
@@ -33,7 +33,6 @@
#include <backend/Handle.h>
#include <utils/BitmaskEnum.h>
#include <utils/bitset.h>
#include <utils/CString.h>
#include <tsl/robin_map.h>
@@ -53,7 +52,6 @@ class FTexture;
class FMaterialInstance : public MaterialInstance {
public:
FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept;
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
FMaterialInstance(const FMaterialInstance& rhs) = delete;
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
@@ -209,17 +207,6 @@ public:
}
}
void setDefaultInstance(bool value) noexcept {
mIsDefaultInstance = value;
}
bool isDefaultInstance() const noexcept {
return mIsDefaultInstance;
}
// Called by the engine to ensure that unset samplers are initialized with placedholders.
void fixMissingSamplers() const;
const char* getName() const noexcept;
void setParameter(std::string_view name,
@@ -249,6 +236,9 @@ private:
template<typename T>
T getParameterImpl(std::string_view name) const;
// initialize the default instance
FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept;
// keep these grouped, they're accessed together in the render-loop
FMaterial const* mMaterial = nullptr;
@@ -275,7 +265,6 @@ private:
bool mDepthWrite : 1;
bool mHasScissor : 1;
bool mIsDoubleSided : 1;
bool mIsDefaultInstance : 1;
TransparencyMode mTransparencyMode : 2;
uint64_t mMaterialSortingKey = 0;
@@ -287,7 +276,6 @@ private:
};
utils::CString mName;
mutable utils::bitset64 mMissingSamplerDescriptors{};
mutable std::once_flag mMissingSamplersFlag;
};
+9 -56
View File
@@ -21,18 +21,8 @@
#include "FilamentAPI-impl.h"
#include <filament/RenderTarget.h>
#include <utils/compiler.h>
#include <utils/BitmaskEnum.h>
#include <utils/Panic.h>
#include <algorithm>
#include <iterator>
#include <limits>
#include <stdint.h>
#include <stddef.h>
#include <filament/RenderTarget.h>
namespace filament {
@@ -44,9 +34,6 @@ struct RenderTarget::BuilderDetails {
uint32_t mWidth{};
uint32_t mHeight{};
uint8_t mSamples = 1; // currently not settable in the public facing API
// The number of layers for the render target. The value should be 1 except for multiview.
// If multiview is enabled, this value is appropriately updated based on the layerCount value
// from each attachment. Hence, #>1 means using multiview
uint8_t mLayerCount = 1;
};
@@ -78,14 +65,6 @@ RenderTarget::Builder& RenderTarget::Builder::layer(AttachmentPoint pt, uint32_t
return *this;
}
RenderTarget::Builder& RenderTarget::Builder::multiview(AttachmentPoint pt, uint8_t layerCount,
uint8_t baseLayer/*= 0*/) noexcept {
mImpl->mAttachments[(size_t)pt].layer = baseLayer;
mImpl->mAttachments[(size_t)pt].layerCount = layerCount;
return *this;
}
RenderTarget* RenderTarget::Builder::build(Engine& engine) {
using backend::TextureUsage;
const FRenderTarget::Attachment& color = mImpl->mAttachments[(size_t)AttachmentPoint::COLOR0];
@@ -94,15 +73,11 @@ RenderTarget* RenderTarget::Builder::build(Engine& engine) {
if (color.texture) {
FILAMENT_CHECK_PRECONDITION(color.texture->getUsage() & TextureUsage::COLOR_ATTACHMENT)
<< "Texture usage must contain COLOR_ATTACHMENT";
FILAMENT_CHECK_PRECONDITION(color.texture->getTarget() != Texture::Sampler::SAMPLER_EXTERNAL)
<< "Color attachment can't be an external texture";
}
if (depth.texture) {
FILAMENT_CHECK_PRECONDITION(depth.texture->getUsage() & TextureUsage::DEPTH_ATTACHMENT)
<< "Texture usage must contain DEPTH_ATTACHMENT";
FILAMENT_CHECK_PRECONDITION(depth.texture->getTarget() != Texture::Sampler::SAMPLER_EXTERNAL)
<< "Depth attachment can't be an external texture";
}
const size_t maxDrawBuffers = downcast(engine).getDriverApi().getMaxDrawBuffers();
@@ -116,48 +91,36 @@ RenderTarget* RenderTarget::Builder::build(Engine& engine) {
uint32_t maxWidth = 0;
uint32_t minHeight = std::numeric_limits<uint32_t>::max();
uint32_t maxHeight = 0;
uint32_t minLayerCount = std::numeric_limits<uint32_t>::max();
uint32_t maxLayerCount = 0;
uint32_t minDepth = std::numeric_limits<uint32_t>::max();
uint32_t maxDepth = 0;
for (auto const& attachment : mImpl->mAttachments) {
if (attachment.texture) {
const uint32_t w = attachment.texture->getWidth(attachment.mipLevel);
const uint32_t h = attachment.texture->getHeight(attachment.mipLevel);
const uint32_t d = attachment.texture->getDepth(attachment.mipLevel);
const uint32_t l = attachment.layerCount;
if (l > 0) {
FILAMENT_CHECK_PRECONDITION(
attachment.texture->getTarget() == Texture::Sampler::SAMPLER_2D_ARRAY)
<< "Texture sampler must be of 2d array for multiview";
}
FILAMENT_CHECK_PRECONDITION(attachment.layer + l <= d)
<< "layer + layerCount cannot exceed the number of depth";
minWidth = std::min(minWidth, w);
minHeight = std::min(minHeight, h);
minLayerCount = std::min(minLayerCount, l);
minDepth = std::min(minDepth, d);
maxWidth = std::max(maxWidth, w);
maxHeight = std::max(maxHeight, h);
maxLayerCount = std::max(maxLayerCount, l);
maxDepth = std::max(maxDepth, d);
}
}
FILAMENT_CHECK_PRECONDITION(minWidth == maxWidth && minHeight == maxHeight
&& minLayerCount == maxLayerCount) << "All attachments dimensions must match";
&& minDepth == maxDepth) << "All attachments dimensions must match";
mImpl->mWidth = minWidth;
mImpl->mHeight = minHeight;
if (minLayerCount > 0) {
// mLayerCount should be 1 except for multiview use where we update this variable
// to the number of layerCount for multiview.
mImpl->mLayerCount = minLayerCount;
}
mImpl->mLayerCount = minDepth;
return downcast(engine).createRenderTarget(*this);
}
// ------------------------------------------------------------------------------------------------
FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& builder)
: mSupportedColorAttachmentsCount(engine.getDriverApi().getMaxDrawBuffers()),
mSupportsReadPixels(false) {
: mSupportedColorAttachmentsCount(engine.getDriverApi().getMaxDrawBuffers()) {
std::copy(std::begin(builder.mImpl->mAttachments), std::end(builder.mImpl->mAttachments),
std::begin(mAttachments));
@@ -189,16 +152,6 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build
(TextureUsage::SAMPLEABLE | Texture::Usage::SUBPASS_INPUT))) {
mSampleableAttachmentsMask |= targetBufferBit;
}
// readPixels() only applies to the color attachment that binds at index 0.
if (i == 0 && any(attachment.texture->getUsage() & TextureUsage::COLOR_ATTACHMENT)) {
// TODO: the following will be changed to
// mSupportsReadPixels =
// any(attachment.texture->getUsage() & TextureUsage::BLIT_SRC);
// in a later filament version when clients have properly added the right usage.
mSupportsReadPixels = attachment.texture->hasBlitSrcUsage();
}
}
}
-8
View File
@@ -39,9 +39,6 @@ public:
uint8_t mipLevel = 0;
CubemapFace face = RenderTarget::CubemapFace::POSITIVE_X;
uint32_t layer = 0;
// Indicates the number of layers used for multiview, starting from the `layer` (baseIndex).
// This means `layer` + `layerCount` cannot exceed the number of depth for the attachment.
uint16_t layerCount = 0;
};
FRenderTarget(FEngine& engine, const Builder& builder);
@@ -68,10 +65,6 @@ public:
bool hasSampleableDepth() const noexcept;
bool supportsReadPixels() const noexcept {
return mSupportsReadPixels;
}
private:
friend class RenderTarget;
static constexpr size_t ATTACHMENT_COUNT = MAX_SUPPORTED_COLOR_ATTACHMENTS_COUNT + 1u;
@@ -80,7 +73,6 @@ private:
backend::TargetBufferFlags mAttachmentMask = {};
backend::TargetBufferFlags mSampleableAttachmentsMask = {};
const uint8_t mSupportedColorAttachmentsCount;
bool mSupportsReadPixels = false;
};
FILAMENT_DOWNCAST(RenderTarget)
+7 -26
View File
@@ -455,15 +455,6 @@ void FRenderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, u
void FRenderer::readPixels(FRenderTarget* renderTarget,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer) {
// TODO: change the following to an assert when client call sites have addressed the issue.
if (!renderTarget->supportsReadPixels()) {
utils::slog.w << "readPixels() must be called with a renderTarget with COLOR0 created with "
"TextureUsage::BLIT_SRC. This precondition will be asserted in a later "
"release of Filament."
<< utils::io::endl;
}
RendererUtils::readPixels(mEngine.getDriverApi(), renderTarget->getHwHandle(),
xoffset, yoffset, width, height, std::move(buffer));
}
@@ -545,12 +536,6 @@ void FRenderer::renderStandaloneView(FView const* view) {
renderInternal(view);
driver.endFrame(mFrameId);
// This is a workaround for internal bug b/361822355.
// TODO: properly address the bug and remove this workaround.
if (engine.getBackend() == backend::Backend::VULKAN) {
engine.flushAndWait();
}
}
}
@@ -603,7 +588,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
JobSystem& js = engine.getJobSystem();
FEngine::DriverApi& driver = engine.getDriverApi();
PostProcessManager& ppm = engine.getPostProcessManager();
ppm.setFrameUniforms(driver, view.getFrameUniforms());
ppm.setFrameUniforms(view.getFrameUniforms());
// DEBUG: driver commands must all happen from the same thread. Enforce that on debug builds.
driver.debugThreading();
@@ -811,7 +796,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
passBuilder.renderFlags(renderFlags);
Variant variant;
variant.setDirectionalLighting(view.hasDirectionalLighting());
variant.setDirectionalLighting(view.hasDirectionalLight());
variant.setDynamicLighting(view.hasDynamicLighting());
variant.setFog(view.hasFog());
variant.setVsm(view.hasShadowing() && view.getShadowType() != ShadowType::PCF);
@@ -972,18 +957,15 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
});
const auto picking = picking_;
if (view.hasPicking()) {
struct PickingResolvePassData {
FrameGraphId<FrameGraphTexture> picking;
};
fg.addPass<PickingResolvePassData>(
"Picking Resolve Pass",
fg.addPass<PickingResolvePassData>("Picking Resolve Pass",
[&](FrameGraph::Builder& builder, auto& data) {
// Note that BLIT_SRC is needed because this texture will be read later (via
// readPixels()).
data.picking =
builder.read(picking, FrameGraphTexture::Usage::COLOR_ATTACHMENT |
FrameGraphTexture::Usage::BLIT_SRC);
data.picking = builder.read(picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
builder.declareRenderPass("Picking Resolve Target", {
.attachments = { .color = { data.picking }}
});
@@ -1059,7 +1041,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// (i.e. it won't be culled, unless everything is culled), so no need to complexify things.
passBuilder.variant(variant);
// This is optional, if not set, the per-view descriptor-set must be set before calling execute()
passBuilder.colorPassDescriptorSet(&view.getColorPassDescriptorSet());
// color-grading as subpass is done either by the color pass or the TAA pass if any
@@ -1209,7 +1190,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// Debug: CSM visualisation
if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades &&
view.hasShadowing() && view.hasDirectionalLighting())) {
view.hasShadowing() && view.hasDirectionalLight())) {
input = ppm.debugShadowCascades(fg, input, depth);
}
+1 -1
View File
@@ -200,7 +200,7 @@ void updateDataAt(backend::DriverApi& driver,
size_t const elementSize = sizeof(float2);
size_t const size = getSkinningBufferSize(count);
auto* out = (float2*)malloc(size);
memcpy(out, pairs.begin(), size);
std::memcpy(out, pairs.begin(), size);
size_t const textureWidth = getSkinningBufferWidth(count);
size_t const lineCount = count / textureWidth;
+2 -3
View File
@@ -70,10 +70,9 @@ void FSwapChain::terminate(FEngine& engine) noexcept {
}
void FSwapChain::setFrameScheduledCallback(
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) {
backend::CallbackHandler* handler, FrameScheduledCallback&& callback) {
mFrameScheduledCallbackIsSet = bool(callback);
mEngine.getDriverApi().setFrameScheduledCallback(
mHwSwapChain, handler, std::move(callback), flags);
mEngine.getDriverApi().setFrameScheduledCallback(mHwSwapChain, handler, std::move(callback));
}
bool FSwapChain::isFrameScheduledCallbackSet() const noexcept {
+1 -1
View File
@@ -79,7 +79,7 @@ public:
}
void setFrameScheduledCallback(
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
backend::CallbackHandler* handler, FrameScheduledCallback&& callback);
bool isFrameScheduledCallbackSet() const noexcept;
+16 -37
View File
@@ -79,7 +79,6 @@ struct Texture::BuilderDetails {
Sampler mTarget = Sampler::SAMPLER_2D;
InternalFormat mFormat = InternalFormat::RGBA8;
Usage mUsage = Usage::NONE;
bool mHasBlitSrc = false;
bool mTextureIsSwizzled = false;
std::array<Swizzle, 4> mSwizzle = {
Swizzle::CHANNEL_0, Swizzle::CHANNEL_1,
@@ -184,15 +183,6 @@ Texture* Texture::Builder::build(Engine& engine) {
}
}
// TODO: remove in a future filament release.
// Clients might not have known that textures that are read need to have BLIT_SRC as usages. For
// now, we workaround the issue by making sure any color attachment can be the source of a copy
// for readPixels().
mImpl->mHasBlitSrc = any(mImpl->mUsage & TextureUsage::BLIT_SRC);
if (!mImpl->mHasBlitSrc && any(mImpl->mUsage & TextureUsage::COLOR_ATTACHMENT)) {
mImpl->mUsage |= TextureUsage::BLIT_SRC;
}
const bool sampleable = bool(mImpl->mUsage & TextureUsage::SAMPLEABLE);
const bool swizzled = mImpl->mTextureIsSwizzled;
const bool imported = mImpl->mImportedId;
@@ -242,17 +232,15 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) {
mLevelCount = builder->mLevels;
mSwizzle = builder->mSwizzle;
mTextureIsSwizzled = builder->mTextureIsSwizzled;
mHasBlitSrc = builder->mHasBlitSrc;
bool const isImported = builder->mImportedId != 0;
if (mTarget == SamplerType::SAMPLER_EXTERNAL && !isImported) {
if (mTarget == SamplerType::SAMPLER_EXTERNAL) {
// mHandle and mHandleForSampling will be created in setExternalImage()
// If this Texture is used for sampling before setExternalImage() is called,
// we'll lazily create a 1x1 placeholder texture.
return;
}
if (UTILS_LIKELY(!isImported)) {
if (UTILS_LIKELY(builder->mImportedId == 0)) {
mHandle = driver.createTexture(
mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage);
} else {
@@ -497,29 +485,15 @@ void FTexture::setExternalImage(FEngine& engine, void* image, size_t plane) noex
}
void FTexture::setExternalStream(FEngine& engine, FStream* stream) noexcept {
if (mTarget != Sampler::SAMPLER_EXTERNAL) {
return;
}
auto& api = engine.getDriverApi();
auto texture = api.createTexture(
mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage);
if (mTextureIsSwizzled) {
auto const& s = mSwizzle;
auto swizzleView = api.createTextureViewSwizzle(texture, s[0], s[1], s[2], s[3]);
api.destroyTexture(texture);
texture = swizzleView;
}
setHandles(texture);
if (stream) {
FILAMENT_CHECK_PRECONDITION(mTarget == Sampler::SAMPLER_EXTERNAL)
<< "Texture target must be SAMPLER_EXTERNAL";
mStream = stream;
api.setExternalStream(mHandle, stream->getHandle());
engine.getDriverApi().setExternalStream(mHandle, stream->getHandle());
} else {
mStream = nullptr;
api.setExternalStream(mHandle, backend::Handle<backend::HwStream>());
engine.getDriverApi().setExternalStream(mHandle, backend::Handle<backend::HwStream>());
}
}
@@ -560,6 +534,10 @@ void FTexture::updateLodRange(uint8_t baseLevel, uint8_t levelCount) noexcept {
range.first = std::min(range.first, baseLevel);
range.last = std::max(range.last, last);
}
if (range.first == 0 && range.last == mLevelCount) {
// the whole range lod range is used, we don't need the view anymore
range.first = range.last = 0;
}
// We defer the creation of the texture view to getHwHandleForSampling() because it
// is a common case that by then, the view won't be needed. Creating the first view on a
// texture has a backend cost.
@@ -604,15 +582,16 @@ backend::Handle<backend::HwTexture> FTexture::getHwHandleForSampling() const noe
if (UTILS_UNLIKELY(mTarget == SamplerType::SAMPLER_EXTERNAL && !mHandleForSampling)) {
return setHandleForSampling(createPlaceholderTexture(*mDriver));
}
auto const& range = mLodRange;
auto& activeRange = mActiveLodRange;
bool const lodRangeChanged = activeRange.first != range.first || activeRange.last != range.last;
if (UTILS_UNLIKELY(lodRangeChanged)) {
if (UTILS_UNLIKELY(activeRange.first != range.first || activeRange.last != range.last)) {
activeRange = range;
if (range.empty() || hasAllLods(range)) {
if (range.empty()) {
setHandleForSampling(mHandle);
} else {
setHandleForSampling(mDriver->createTextureView(mHandle, range.first, range.size()));
setHandleForSampling(
mDriver->createTextureView(mHandle, range.first, range.last - range.first));
}
}
return mHandleForSampling;
+1 -18
View File
@@ -123,11 +123,6 @@ public:
bool textureHandleCanMutate() const noexcept;
void updateLodRange(uint8_t level) noexcept;
// TODO: remove in a future filament release. See below for description.
inline bool hasBlitSrcUsage() const noexcept {
return mHasBlitSrc;
}
private:
friend class Texture;
struct LodRange {
@@ -135,13 +130,7 @@ private:
uint8_t first = 0; // first lod
uint8_t last = 0; // 1 past last lod
bool empty() const noexcept { return first == last; }
size_t size() const noexcept { return last - first; }
};
bool hasAllLods(LodRange const range) const noexcept {
return range.first == 0 && range.last == mLevelCount;
}
void updateLodRange(uint8_t baseLevel, uint8_t levelCount) noexcept;
void setHandles(backend::Handle<backend::HwTexture> handle) noexcept;
backend::Handle<backend::HwTexture> setHandleForSampling(
@@ -168,13 +157,7 @@ private:
bool mTextureIsSwizzled;
Usage mUsage = Usage::DEFAULT;
// TODO: remove in a future filament release.
// Indicates whether the user has set the TextureUsage::BLIT_SRC usage. This will be used to
// temporarily validate whether this texture can be used for readPixels.
bool mHasBlitSrc = false;
// there is 5 bytes of padding here
// there is 7 bytes of padding here
FStream* mStream = nullptr; // only needed for streaming textures
};
+2 -2
View File
@@ -142,7 +142,7 @@ void FView::terminate(FEngine& engine) {
ShadowMapManager::terminate(engine, mShadowMapManager);
mUniforms.terminate(driver);
mColorPassDescriptorSet.terminate(engine.getDescriptorSetLayoutFactory(), driver);
mColorPassDescriptorSet.terminate(driver);
mFroxelizer.terminate(driver);
mCommonRenderableDescriptorSet.terminate(driver);
@@ -584,7 +584,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
// we also know if we have a directional light
FLightManager::Instance const directionalLight =
lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
mHasDirectionalLighting = directionalLight.isValid();
mHasDirectionalLight = directionalLight.isValid();
// As soon as prepareVisibleLight finishes, we can kick-off the froxelization
if (hasDynamicLighting()) {
+2 -10
View File
@@ -175,17 +175,9 @@ public:
utils::JobSystem::Job* getFroxelizerSync() const noexcept { return mFroxelizerSync; }
void setFroxelizerSync(utils::JobSystem::Job* sync) noexcept { mFroxelizerSync = sync; }
// ultimately decides to use the DIR variant
bool hasDirectionalLighting() const noexcept { return mHasDirectionalLighting; }
// ultimately decides to use the DYN variant
bool hasDirectionalLight() const noexcept { return mHasDirectionalLight; }
bool hasDynamicLighting() const noexcept { return mHasDynamicLighting; }
// ultimately decides to use the SRE variant
bool hasShadowing() const noexcept { return mHasShadowing; }
bool needsDirectionalShadowMaps() const noexcept { return mHasShadowing && mHasDirectionalLighting; }
bool needsPointShadowMaps() const noexcept { return mHasShadowing && mHasDynamicLighting; }
bool needsShadowMap() const noexcept { return mNeedsShadowMap; }
bool hasFog() const noexcept { return mFogOptions.enabled && mFogOptions.density > 0.0f; }
bool hasVSM() const noexcept { return mShadowType == ShadowType::VSM; }
@@ -581,7 +573,7 @@ private:
Range mVisibleDirectionalShadowCasters;
Range mSpotLightShadowCasters;
uint32_t mRenderableUBOSize = 0;
mutable bool mHasDirectionalLighting = false;
mutable bool mHasDirectionalLight = false;
mutable bool mHasDynamicLighting = false;
mutable bool mHasShadowing = false;
mutable bool mNeedsShadowMap = false;
+2 -4
View File
@@ -17,7 +17,6 @@
#include "ColorPassDescriptorSet.h"
#include "Froxelizer.h"
#include "HwDescriptorSetLayoutFactory.h"
#include "ShadowMapManager.h"
#include "TypedUniformBuffer.h"
@@ -101,7 +100,6 @@ ColorPassDescriptorSet::ColorPassDescriptorSet(FEngine& engine,
for (bool const fog: { false, true }) {
auto index = ColorPassDescriptorSet::getIndex(lit, ssr, fog);
mDescriptorSetLayout[index] = {
engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(),
descriptor_sets::getPerViewDescriptorSetLayout(
MaterialDomain::SURFACE,
@@ -139,12 +137,12 @@ void ColorPassDescriptorSet::init(
}
}
void ColorPassDescriptorSet::terminate(HwDescriptorSetLayoutFactory& factory, DriverApi& driver) {
void ColorPassDescriptorSet::terminate(DriverApi& driver) {
for (auto&& entry : mDescriptorSet) {
entry.terminate(driver);
}
for (auto&& entry : mDescriptorSetLayout) {
entry.terminate(factory, driver);
entry.terminate(driver);
}
}
+1 -2
View File
@@ -43,7 +43,6 @@
namespace filament {
class DescriptorSetLayout;
class HwDescriptorSetLayoutFactory;
struct AmbientOcclusionOptions;
struct DynamicResolutionOptions;
@@ -89,7 +88,7 @@ public:
backend::BufferObjectHandle recordBuffer,
backend::BufferObjectHandle froxelBuffer) noexcept;
void terminate(HwDescriptorSetLayoutFactory& factory, backend::DriverApi& driver);
void terminate(backend::DriverApi& driver);
void prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept;
void prepareLodBias(float bias, math::float2 derivativesScale) noexcept;

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