Compare commits
81
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69d5ac2107 | ||
|
|
c01d2c09b6 | ||
|
|
f37d8b2fc2 | ||
|
|
6397fa3fe1 | ||
|
|
37d1baf0e8 | ||
|
|
1596a5ec71 | ||
|
|
22e62b92a6 | ||
|
|
8f979dacaf | ||
|
|
e3cb67045b | ||
|
|
f4cd9f712a | ||
|
|
99b3587dd3 | ||
|
|
8e30b1bfc9 | ||
|
|
c0b1daf339 | ||
|
|
efb94da111 | ||
|
|
c538bded6f | ||
|
|
f6d9db14c3 | ||
|
|
91ce3a9375 | ||
|
|
3aeee1630a | ||
|
|
15d77ed1d7 | ||
|
|
6a64cdb059 | ||
|
|
cf0cb66b28 | ||
|
|
3a2333d709 | ||
|
|
6790a1238b | ||
|
|
fad5d57053 | ||
|
|
e95f0ed6a0 | ||
|
|
73b0be799c | ||
|
|
6de6a2123d | ||
|
|
d161ef24f5 | ||
|
|
c3ea1e45f9 | ||
|
|
c6c20df474 | ||
|
|
bbb45218ac | ||
|
|
02bdddf50e | ||
|
|
21d93ce49d | ||
|
|
cab5fc561c | ||
|
|
c780804fd6 | ||
|
|
b6d6f8f6e4 | ||
|
|
f098d08c48 | ||
|
|
5c08f34e8c | ||
|
|
a37d03101e | ||
|
|
c0819b781a | ||
|
|
d9bf68ed06 | ||
|
|
32ac10161b | ||
|
|
d073fb5342 | ||
|
|
8fe7c1ff30 | ||
|
|
3b12e54573 | ||
|
|
a1f146ebea | ||
|
|
9401d6be9e | ||
|
|
111be6ef6d | ||
|
|
bd0ca7a9bd | ||
|
|
8797f45bd0 | ||
|
|
f04a25028e | ||
|
|
bb1f58e97c | ||
|
|
189e36d539 | ||
|
|
cba819be8f | ||
|
|
41e1e7e54b | ||
|
|
d8580d93ad | ||
|
|
fef40a8c5d | ||
|
|
d299b1f00e | ||
|
|
daa2790c4e | ||
|
|
d2cb53e39d | ||
|
|
315c8d75bc | ||
|
|
c19785b72f | ||
|
|
38b3074835 | ||
|
|
929ad27606 | ||
|
|
c12bcbb21e | ||
|
|
07f07e0700 | ||
|
|
7e0181b2cf | ||
|
|
b4162eb313 | ||
|
|
a0dc7104b6 | ||
|
|
80f151ccf7 | ||
|
|
26bb99c64e | ||
|
|
1612cdb353 | ||
|
|
e75882e4d4 | ||
|
|
2e763149e7 | ||
|
|
e40a4c69a3 | ||
|
|
9a1ee1ff67 | ||
|
|
9fb692b2f6 | ||
|
|
ac7c679783 | ||
|
|
38a3c52629 | ||
|
|
29ecfd5fda | ||
|
|
f11a04722b |
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
""},
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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<
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
}
|
||||
|
||||
void setFrameScheduledCallback(
|
||||
backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags);
|
||||
backend::CallbackHandler* handler, FrameScheduledCallback&& callback);
|
||||
|
||||
bool isFrameScheduledCallbackSet() const noexcept;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user