Compare commits
135
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e32e6cf2a | ||
|
|
1d4f1fe71f | ||
|
|
c93aa4c90d | ||
|
|
499939ed3c | ||
|
|
6be97ee01d | ||
|
|
7267696cbf | ||
|
|
85eb724a90 | ||
|
|
6dd6db89d5 | ||
|
|
c76f67c139 | ||
|
|
6fc16bdcda | ||
|
|
4f021583f1 | ||
|
|
ef15a29c0c | ||
|
|
a0472d3c9f | ||
|
|
54a800a25d | ||
|
|
7f8fbe586c | ||
|
|
6f2c45c76d | ||
|
|
ad60008b6a | ||
|
|
d87f9b621b | ||
|
|
1b38cda0d5 | ||
|
|
a1dea7b1fa | ||
|
|
744708b5ca | ||
|
|
e901837317 | ||
|
|
17f71c9b81 | ||
|
|
b056c126fe | ||
|
|
6ac36d1aab | ||
|
|
a1752b363c | ||
|
|
e855f4aafb | ||
|
|
1c693e24cf | ||
|
|
af92a1f21b | ||
|
|
10ff1ee3e4 | ||
|
|
5449f46350 | ||
|
|
2967888d26 | ||
|
|
afe81d6889 | ||
|
|
95134883d6 | ||
|
|
600c122761 | ||
|
|
1cf5335964 | ||
|
|
4ac4ffd8d6 | ||
|
|
6c40721695 | ||
|
|
56fa078376 | ||
|
|
883507a26f | ||
|
|
d697d42c4a | ||
|
|
e7feee7d5b | ||
|
|
4c75e6e9a2 | ||
|
|
8ab8525359 | ||
|
|
65c794881f | ||
|
|
08d6d57ad0 | ||
|
|
254cf15f0e | ||
|
|
22d99bac3d | ||
|
|
420f06bef3 | ||
|
|
bf8bcef35c | ||
|
|
bfab9f9c32 | ||
|
|
c8335fade7 | ||
|
|
99eac62b4e | ||
|
|
6146d071ba | ||
|
|
d26c972f5b | ||
|
|
93dc218b83 | ||
|
|
b56379dc0f | ||
|
|
e4442a5eb2 | ||
|
|
490e8cf0d0 | ||
|
|
0d25f79421 | ||
|
|
d0586743f6 | ||
|
|
14a99cbc63 | ||
|
|
808b51c1cf | ||
|
|
315726678e | ||
|
|
bac8e58ee7 | ||
|
|
ec31a516f7 | ||
|
|
7a3b762b53 | ||
|
|
67268cb76d | ||
|
|
0b34dc6608 | ||
|
|
afe292986e | ||
|
|
83acf0f9de | ||
|
|
69d9c8b72c | ||
|
|
3bffd90357 | ||
|
|
b21dfd5ea2 | ||
|
|
8af2d7512d | ||
|
|
785592293c | ||
|
|
613b1fc246 | ||
|
|
c8098a4c15 | ||
|
|
d8eee061e8 | ||
|
|
0ebe888344 | ||
|
|
c20496a461 | ||
|
|
8955a61df3 | ||
|
|
9e0e3262c3 | ||
|
|
d3d5281488 | ||
|
|
075f3d0869 | ||
|
|
3ca9b9cd0f | ||
|
|
eb33ce35ef | ||
|
|
cddd22da28 | ||
|
|
05ce5ead8d | ||
|
|
4bed88289f | ||
|
|
7a6138597e | ||
|
|
28da328815 | ||
|
|
de3c4b6d56 | ||
|
|
5d27240e0c | ||
|
|
94fc37b8c2 | ||
|
|
532cb28ce9 | ||
|
|
b30d12aca5 | ||
|
|
d891a6927f | ||
|
|
22b5715837 | ||
|
|
7eb08f11ea | ||
|
|
588a05f77c | ||
|
|
3ec2b47df7 | ||
|
|
1c55ad49ee | ||
|
|
348e61fd76 | ||
|
|
8ce6d54930 | ||
|
|
8db5e700d7 | ||
|
|
0ec3596440 | ||
|
|
ee4c03d0f1 | ||
|
|
f12e7f9fbf | ||
|
|
a068143953 | ||
|
|
90d90094dc | ||
|
|
54df4524eb | ||
|
|
d70f2e1b81 | ||
|
|
2f359b73a4 | ||
|
|
abb0cbc98e | ||
|
|
2763931b47 | ||
|
|
0ead96b606 | ||
|
|
317c1bb7ea | ||
|
|
9aaaad9271 | ||
|
|
d2ce714e73 | ||
|
|
8398175d9c | ||
|
|
26258a4718 | ||
|
|
3644e7f808 | ||
|
|
4d774820d9 | ||
|
|
aad45d9119 | ||
|
|
5389b37002 | ||
|
|
d5fe9e236f | ||
|
|
59890ac85a | ||
|
|
435969f565 | ||
|
|
e3db39105f | ||
|
|
dfa821d351 | ||
|
|
1ab223c965 | ||
|
|
29bb60cd94 | ||
|
|
93b15dac87 | ||
|
|
a8fda9b4d0 |
+13
-19
@@ -73,7 +73,6 @@ The following CMake options are boolean options specific to Filament:
|
||||
- `FILAMENT_SUPPORTS_VULKAN`: Include the Vulkan backend
|
||||
- `FILAMENT_INSTALL_BACKEND_TEST`: Install the backend test library so it can be consumed on iOS
|
||||
- `FILAMENT_USE_EXTERNAL_GLES3`: Experimental: Compile Filament against OpenGL ES 3
|
||||
- `FILAMENT_USE_SWIFTSHADER`: Compile Filament against SwiftShader
|
||||
- `FILAMENT_SKIP_SAMPLES`: Don't build sample apps
|
||||
|
||||
To turn an option on or off:
|
||||
@@ -426,7 +425,7 @@ value is the desired roughness between 0 and 1.
|
||||
|
||||
## Generating C++ documentation
|
||||
|
||||
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
|
||||
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
|
||||
following commands:
|
||||
|
||||
```shell
|
||||
@@ -436,32 +435,27 @@ doxygen docs/doxygen/filament.doxygen
|
||||
|
||||
Finally simply open `docs/html/index.html` in your web browser.
|
||||
|
||||
## SwiftShader
|
||||
## Software Rasterization
|
||||
|
||||
To try out Filament's Vulkan support with SwiftShader, first build SwiftShader and set the
|
||||
`SWIFTSHADER_LD_LIBRARY_PATH` variable to the folder that contains `libvk_swiftshader.dylib`:
|
||||
We have tested swiftshader for running software rasterization on the Vulkan backend. To use this,
|
||||
please first make sure that the [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) is installed on
|
||||
your machine. If you are doing a manual installation of the SDK on Linux, you will have to source
|
||||
`setup-env.sh` in the SDK's root folder to make sure the Vulkan loader is the first lib loaded.
|
||||
|
||||
### Swiftshader (Vulkan) [tested on macOS and Linux]
|
||||
|
||||
First, build SwiftShader
|
||||
|
||||
```shell
|
||||
git clone https://github.com/google/swiftshader.git
|
||||
cd swiftshader/build
|
||||
cmake .. && make -j
|
||||
export SWIFTSHADER_LD_LIBRARY_PATH=`pwd`
|
||||
```
|
||||
|
||||
Next, go to your Filament repo and use the [easy build](#easy-build) script with `-t`.
|
||||
|
||||
## SwiftShader for CI
|
||||
|
||||
Continuous testing turnaround can be quite slow if you need to build SwiftShader from scratch, so we
|
||||
provide an Ubuntu-based Docker image that has it already built. The Docker image also includes
|
||||
everything necessary for building Filament. You can fetch and run the image as follows:
|
||||
|
||||
and then set `VK_ICD_FILENAMES` to the ICD json produced in the build. For example,
|
||||
```shell
|
||||
docker pull ghcr.io/filament-assets/swiftshader
|
||||
docker run -it ghcr.io/filament-assets/swiftshader
|
||||
export VK_ICD_FILENAMES=/Users/user/swiftshader/build/Darwin/vk_swiftshader_icd.json
|
||||
```
|
||||
|
||||
To do more with the container, see the helper script at `build/swiftshader/test.sh`.
|
||||
Build Filament as normal and use the vulkan backend.
|
||||
|
||||
If you are a team member, you can update the public image to the latest SwiftShader by
|
||||
following the instructions at the top of `build/swiftshader/Dockerfile`.
|
||||
|
||||
+21
-37
@@ -21,8 +21,6 @@ project(TNT)
|
||||
# ==================================================================================================
|
||||
option(FILAMENT_USE_EXTERNAL_GLES3 "Experimental: Compile Filament against OpenGL ES 3" OFF)
|
||||
|
||||
option(FILAMENT_USE_SWIFTSHADER "Compile Filament against SwiftShader" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_LTO "Enable link-time optimizations if supported by the compiler" OFF)
|
||||
|
||||
option(FILAMENT_SKIP_SAMPLES "Don't build samples" OFF)
|
||||
@@ -71,6 +69,10 @@ set(FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB "8" CACHE STRING
|
||||
"Size of the Metal handle arena, default 8."
|
||||
)
|
||||
|
||||
set(FILAMENT_BACKEND_DEBUG_FLAG "" CACHE STRING
|
||||
"A debug flag meant for enabling/disabling backend debugging paths"
|
||||
)
|
||||
|
||||
# Enable exceptions by default in spirv-cross.
|
||||
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
|
||||
|
||||
@@ -141,11 +143,6 @@ if (LINUX)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_XCB)
|
||||
endif()
|
||||
|
||||
# Default Swiftshader build does not enable the xlib extension
|
||||
if (FILAMENT_SUPPORTS_XLIB AND FILAMENT_USE_SWIFTSHADER)
|
||||
set(FILAMENT_SUPPORTS_XLIB OFF)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_XLIB)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_XLIB)
|
||||
endif()
|
||||
@@ -169,15 +166,6 @@ if (NOT ANDROID AND NOT WEBGL AND NOT IOS AND NOT FILAMENT_LINUX_IS_MOBILE)
|
||||
set(IS_HOST_PLATFORM TRUE)
|
||||
endif()
|
||||
|
||||
if (IOS)
|
||||
# Remove the headerpad_max_install_names linker flag on iOS. It causes warnings when linking
|
||||
# executables with bitcode.
|
||||
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_C_LINK_FLAGS ${CMAKE_C_LINK_FLAGS})
|
||||
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_CXX_LINK_FLAGS ${CMAKE_CXX_LINK_FLAGS})
|
||||
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS ${CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS})
|
||||
string(REPLACE "-Wl,-headerpad_max_install_names" "" CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS ${CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS})
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# Link statically against c/c++ lib to avoid missing redistriburable such as
|
||||
# "VCRUNTIME140.dll not found. Try reinstalling the app.", but give users
|
||||
@@ -332,10 +320,6 @@ if (FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
set(EGL TRUE)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_USE_SWIFTSHADER)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFILAMENT_USE_SWIFTSHADER")
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_USE_MATH_DEFINES=1")
|
||||
endif()
|
||||
@@ -389,8 +373,9 @@ endif()
|
||||
if (NOT MSVC AND NOT IOS)
|
||||
# Omitting stack frame pointers prevents the generation of readable stack traces in crash reports on iOS
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fomit-frame-pointer")
|
||||
endif()
|
||||
|
||||
# These aren't compatible with -fembed-bitcode (and seem to have no effect on Apple platforms anyway)
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffunction-sections -fdata-sections")
|
||||
endif()
|
||||
|
||||
@@ -455,8 +440,13 @@ endif()
|
||||
if (NOT WEBGL)
|
||||
set(GC_SECTIONS "-Wl,--gc-sections")
|
||||
endif()
|
||||
|
||||
set(B_SYMBOLIC_FUNCTIONS "-Wl,-Bsymbolic-functions")
|
||||
|
||||
if (ANDROID)
|
||||
set(BINARY_ALIGNMENT "-Wl,-z,max-page-size=16384")
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(GC_SECTIONS "-Wl,-dead_strip")
|
||||
set(B_SYMBOLIC_FUNCTIONS "")
|
||||
@@ -470,7 +460,7 @@ if (APPLE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GC_SECTIONS}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS} ${BINARY_ALIGNMENT}")
|
||||
|
||||
if (WEBGL_PTHREADS)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread")
|
||||
@@ -548,6 +538,12 @@ if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview")
|
||||
set(FILAMENT_ENABLE_MULTIVIEW ON)
|
||||
endif ()
|
||||
|
||||
# Define backend flag for debug only
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT FILAMENT_BACKEND_DEBUG_FLAG STREQUAL "")
|
||||
add_definitions(-DFILAMENT_BACKEND_DEBUG_FLAG=${FILAMENT_BACKEND_DEBUG_FLAG})
|
||||
unset(FILAMENT_BACKEND_DEBUG_FLAG)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Material compilation flags
|
||||
# ==================================================================================================
|
||||
@@ -676,21 +672,6 @@ else()
|
||||
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${CMAKE_BUILD_TYPE}.cmake)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Try to find Vulkan if the SDK is installed, otherwise fall back to the bundled version.
|
||||
# This needs to stay in our top-level CMakeLists because it sets up variables that are used by the
|
||||
# "bluevk" and "samples" targets.
|
||||
# ==================================================================================================
|
||||
|
||||
if (FILAMENT_USE_SWIFTSHADER)
|
||||
if (NOT FILAMENT_SUPPORTS_VULKAN)
|
||||
message(ERROR "SwiftShader is only useful when Vulkan is enabled.")
|
||||
endif()
|
||||
find_library(SWIFTSHADER_VK NAMES vk_swiftshader HINTS "$ENV{SWIFTSHADER_LD_LIBRARY_PATH}")
|
||||
message(STATUS "Found SwiftShader VK library in: ${SWIFTSHADER_VK}.")
|
||||
add_definitions(-DFILAMENT_VKLIBRARY_PATH=\"${SWIFTSHADER_VK}\")
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Common Functions
|
||||
# ==================================================================================================
|
||||
@@ -819,6 +800,9 @@ if (IS_HOST_PLATFORM)
|
||||
add_subdirectory(${TOOLS}/glslminifier)
|
||||
add_subdirectory(${TOOLS}/matc)
|
||||
add_subdirectory(${TOOLS}/matinfo)
|
||||
if (NOT WIN32) # matedit not yet supported on Windows
|
||||
add_subdirectory(${TOOLS}/matedit)
|
||||
endif()
|
||||
add_subdirectory(${TOOLS}/mipgen)
|
||||
add_subdirectory(${TOOLS}/normal-blending)
|
||||
add_subdirectory(${TOOLS}/resgen)
|
||||
|
||||
@@ -7,5 +7,3 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()`
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.51.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.51.8'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.51.1'
|
||||
pod 'Filament', '~> 1.51.8'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,6 +7,41 @@ 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.51.9
|
||||
|
||||
|
||||
## v1.51.8
|
||||
|
||||
- filagui: Fix regression which broke WebGL
|
||||
- Add a new Engine::Config setting to control preferred shader language
|
||||
- Add `getEyeIndex` vertex API
|
||||
- ios: Remove bitcode from iOS builds
|
||||
|
||||
## v1.51.7
|
||||
|
||||
- Add new matedit tool
|
||||
- filagui: Support rendering `GL_TEXTURE_EXTERNAL_OES` textures.
|
||||
- `setFrameScheduledCallback` now takes a `utils::Invocable`.
|
||||
- engine: Add `isPaused()`
|
||||
|
||||
## v1.51.6
|
||||
|
||||
- Add new matedit tool
|
||||
- filagui: Support rendering `GL_TEXTURE_EXTERNAL_OES` textures.
|
||||
|
||||
## v1.51.5
|
||||
|
||||
|
||||
## v1.51.4
|
||||
|
||||
|
||||
## v1.51.3
|
||||
|
||||
|
||||
## v1.51.2
|
||||
|
||||
- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()`
|
||||
|
||||
## v1.51.1
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ set(FILAMAT_INCLUDE_DIRS
|
||||
include_directories(${FILAMENT_DIR}/include)
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_SOURCE_DIR}/libfilamat-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_library(filamat-jni SHARED src/main/cpp/MaterialBuilder.cpp)
|
||||
target_include_directories(filamat-jni PRIVATE ${FILAMAT_INCLUDE_DIRS})
|
||||
|
||||
@@ -59,6 +59,7 @@ endif()
|
||||
|
||||
set(VERSION_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/libfilament-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${VERSION_SCRIPT}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_library(filament-jni SHARED
|
||||
src/main/cpp/BufferObject.cpp
|
||||
|
||||
@@ -342,6 +342,21 @@ Java_com_google_android_filament_Engine_nIsValidMaterial(JNIEnv*, jclass,
|
||||
return (jboolean)engine->isValid((Material*)nativeMaterial);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidMaterialInstance(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeMaterial, jlong nativeMaterialInstance) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Material*)nativeMaterial,
|
||||
(MaterialInstance*)nativeMaterialInstance);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidExpensiveMaterialInstance(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeMaterialInstance) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValidExpensive((MaterialInstance*)nativeMaterialInstance);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidSkybox(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeSkybox) {
|
||||
@@ -391,6 +406,13 @@ Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass,
|
||||
engine->flush();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsPaused(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jboolean)engine->isPaused();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Engine_nSetPaused(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jboolean paused) {
|
||||
@@ -495,7 +517,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile,
|
||||
jint stereoscopicType, jlong stereoscopicEyeCount,
|
||||
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
|
||||
jboolean disableHandleUseAfterFreeCheck) {
|
||||
jboolean disableHandleUseAfterFreeCheck,
|
||||
jint preferredShaderLanguage,
|
||||
jboolean forceGLES2Context) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
|
||||
@@ -511,6 +535,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB,
|
||||
.resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge,
|
||||
.disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck,
|
||||
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
|
||||
.forceGLES2Context = (bool) forceGLES2Context,
|
||||
};
|
||||
builder->config(&config);
|
||||
}
|
||||
|
||||
@@ -104,6 +104,14 @@ Java_com_google_android_filament_RenderableManager_nBuilderGeometry__JIIJJIIII(J
|
||||
(size_t) count);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderGeometryType(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, int type) {
|
||||
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
|
||||
builder->geometryType((RenderableManager::Builder::GeometryType)type);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderMaterial(JNIEnv*, jclass,
|
||||
|
||||
@@ -49,6 +49,12 @@ Java_com_google_android_filament_View_nSetCamera(JNIEnv*, jclass,
|
||||
view->setCamera(camera);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_View_nHasCamera(JNIEnv*, jclass, jlong nativeView) {
|
||||
View* view = (View*) nativeView;
|
||||
return (jboolean)view->hasCamera();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetColorGrading(JNIEnv*, jclass,
|
||||
jlong nativeView, jlong nativeColorGrading) {
|
||||
|
||||
@@ -225,7 +225,9 @@ public class Engine {
|
||||
config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile,
|
||||
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
|
||||
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
|
||||
config.disableHandleUseAfterFreeCheck);
|
||||
config.disableHandleUseAfterFreeCheck,
|
||||
config.preferredShaderLanguage.ordinal(),
|
||||
config.forceGLES2Context);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -243,9 +245,11 @@ public class Engine {
|
||||
/**
|
||||
* Sets the initial paused state of the rendering thread.
|
||||
*
|
||||
* <p>Warning: This is an experimental API. See {@link Engine#setPaused(boolean)} for
|
||||
* caveats.
|
||||
*
|
||||
* @param paused Whether to start the rendering thread paused.
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
* @warning Experimental.
|
||||
*/
|
||||
public Builder paused(boolean paused) {
|
||||
nSetBuilderPaused(mNativeBuilder, paused);
|
||||
@@ -426,6 +430,35 @@ public class Engine {
|
||||
* 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
|
||||
* METAL_LIBRARY (precompiled .metallib). This option controls which shader language is
|
||||
* used when materials contain both.
|
||||
*
|
||||
* By default, when preferredShaderLanguage is unset, Filament will prefer METAL_LIBRARY
|
||||
* shaders if present within a material, falling back to MSL. Setting
|
||||
* preferredShaderLanguage to ShaderLanguage::MSL will instead instruct Filament to check
|
||||
* for the presence of MSL in a material first, falling back to METAL_LIBRARY if MSL is not
|
||||
* present.
|
||||
*
|
||||
* When using a non-Metal backend, setting this has no effect.
|
||||
*/
|
||||
public enum ShaderLanguage {
|
||||
DEFAULT,
|
||||
MSL,
|
||||
METAL_LIBRARY,
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine, Config config) {
|
||||
@@ -847,6 +880,24 @@ public class Engine {
|
||||
return nIsValidMaterial(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidMaterialInstance(@NonNull Material ma, MaterialInstance mi) {
|
||||
return nIsValidMaterialInstance(getNativeObject(), ma.getNativeObject(), mi.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidExpensiveMaterialInstance(@NonNull MaterialInstance object) {
|
||||
return nIsValidExpensiveMaterialInstance(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
@@ -1207,9 +1258,29 @@ public class Engine {
|
||||
nFlush(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paused state of rendering thread.
|
||||
*
|
||||
* <p>Warning: This is an experimental API.
|
||||
*
|
||||
* @see #setPaused
|
||||
*/
|
||||
public boolean isPaused() {
|
||||
return nIsPaused(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause or resume the rendering thread.
|
||||
* @warning Experimental.
|
||||
*
|
||||
* <p>Warning: This is an experimental API. In particular, note the following caveats.
|
||||
*
|
||||
* <ul><li>
|
||||
* Buffer callbacks will never be called as long as the rendering thread is paused.
|
||||
* Do not rely on a buffer callback to unpause the thread.
|
||||
* </li><li>
|
||||
* While the rendering thread is paused, rendering commands will continue to be queued until the
|
||||
* buffer limit is reached. When the limit is reached, the program will abort.
|
||||
* </li></ul>
|
||||
*/
|
||||
public void setPaused(boolean paused) {
|
||||
nSetPaused(getNativeObject(), paused);
|
||||
@@ -1280,6 +1351,8 @@ public class Engine {
|
||||
private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
|
||||
private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight);
|
||||
private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial);
|
||||
private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance);
|
||||
private static native boolean nIsValidExpensiveMaterialInstance(long nativeEngine, long nativeMaterialInstance);
|
||||
private static native boolean nIsValidSkybox(long nativeEngine, long nativeSkybox);
|
||||
private static native boolean nIsValidColorGrading(long nativeEngine, long nativeColorGrading);
|
||||
private static native boolean nIsValidTexture(long nativeEngine, long nativeTexture);
|
||||
@@ -1288,6 +1361,7 @@ public class Engine {
|
||||
private static native void nDestroyEntity(long nativeEngine, int entity);
|
||||
private static native void nFlushAndWait(long nativeEngine);
|
||||
private static native void nFlush(long nativeEngine);
|
||||
private static native boolean nIsPaused(long nativeEngine);
|
||||
private static native void nSetPaused(long nativeEngine, boolean paused);
|
||||
private static native long nGetTransformManager(long nativeEngine);
|
||||
private static native long nGetLightManager(long nativeEngine);
|
||||
@@ -1310,7 +1384,9 @@ public class Engine {
|
||||
long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile,
|
||||
int stereoscopicType, long stereoscopicEyeCount,
|
||||
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
|
||||
boolean disableHandleUseAfterFreeCheck);
|
||||
boolean disableHandleUseAfterFreeCheck,
|
||||
int preferredShaderLanguage,
|
||||
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);
|
||||
|
||||
+27
@@ -175,6 +175,32 @@ public class RenderableManager {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of geometry for a Renderable
|
||||
*/
|
||||
public enum GeometryType {
|
||||
/** dynamic gemoetry has no restriction */
|
||||
DYNAMIC,
|
||||
/** bounds and world space transform are immutable */
|
||||
STATIC_BOUNDS,
|
||||
/** skinning/morphing not allowed and Vertex/IndexBuffer immutables */
|
||||
STATIC
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether this renderable has static bounds. In this context his means that
|
||||
* the renderable's bounding box cannot change and that the renderable's transform is
|
||||
* assumed immutable. Changing the renderable's transform via the TransformManager
|
||||
* can lead to corrupted graphics. Note that skinning and morphing are not forbidden.
|
||||
* Disabled by default.
|
||||
* @param enable whether this renderable has static bounds. false by default.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder geometryType(GeometryType type) {
|
||||
nBuilderGeometryType(mNativeBuilder, type.ordinal());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a material instance to the specified primitive.
|
||||
*
|
||||
@@ -964,6 +990,7 @@ public class RenderableManager {
|
||||
private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer);
|
||||
private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count);
|
||||
private static native void nBuilderGeometry(long nativeBuilder, int index, int value, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int minIndex, int maxIndex, int count);
|
||||
private static native void nBuilderGeometryType(long nativeBuilder, int type);
|
||||
private static native void nBuilderMaterial(long nativeBuilder, int index, long nativeMaterialInstance);
|
||||
private static native void nBuilderBlendOrder(long nativeBuilder, int index, int blendOrder);
|
||||
private static native void nBuilderGlobalBlendOrderEnabled(long nativeBuilder, int index, boolean enabled);
|
||||
|
||||
@@ -241,6 +241,15 @@ public class View {
|
||||
nSetCamera(getNativeObject(), camera == null ? 0 : camera.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Query whether a camera is set.
|
||||
* @return true if a camera is set, false otherwise
|
||||
* @see #setCamera
|
||||
*/
|
||||
public boolean hasCamera() {
|
||||
return nHasCamera(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this View's associated Camera, or null if none has been assigned.
|
||||
*
|
||||
@@ -1238,6 +1247,7 @@ public class View {
|
||||
private static native void nSetName(long nativeView, String name);
|
||||
private static native void nSetScene(long nativeView, long nativeScene);
|
||||
private static native void nSetCamera(long nativeView, long nativeCamera);
|
||||
private static native boolean nHasCamera(long nativeView);
|
||||
private static native void nSetViewport(long nativeView, int left, int bottom, int width, int height);
|
||||
private static native void nSetVisibleLayers(long nativeView, int select, int value);
|
||||
private static native void nSetShadowingEnabled(long nativeView, boolean enabled);
|
||||
|
||||
@@ -31,6 +31,7 @@ set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament-iblprefilter.a)
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_library(filament-utils-jni SHARED
|
||||
src/main/cpp/AutomationEngine.cpp
|
||||
|
||||
@@ -44,6 +44,7 @@ set_target_properties(uberarchive PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberarchive.a)
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
set(GLTFIO_SRCS
|
||||
${GLTFIO_DIR}/include/gltfio/Animator.h
|
||||
@@ -86,7 +87,10 @@ set(GLTFIO_SRCS
|
||||
${GLTFIO_DIR}/src/Wireframe.cpp
|
||||
${GLTFIO_DIR}/src/Wireframe.h
|
||||
${GLTFIO_DIR}/src/downcast.h
|
||||
${GLTFIO_DIR}/src/extended/AssetLoaderExtended.cpp
|
||||
${GLTFIO_DIR}/src/extended/AssetLoaderExtended.h
|
||||
${GLTFIO_DIR}/src/extended/ResourceLoaderExtended.cpp
|
||||
${GLTFIO_DIR}/src/extended/ResourceLoaderExtended.h
|
||||
${GLTFIO_DIR}/src/extended/TangentsJobExtended.cpp
|
||||
${GLTFIO_DIR}/src/extended/TangentsJobExtended.h
|
||||
${GLTFIO_DIR}/src/extended/TangentSpaceMeshWrapper.cpp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.51.1
|
||||
VERSION_NAME=1.51.8
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+1
-1
@@ -321,7 +321,7 @@ class MainActivity : Activity() {
|
||||
var flags = uiHelper.swapChainFlags
|
||||
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
|
||||
if (SwapChain.isSRGBSwapChainSupported(engine)) {
|
||||
flags = flags or SwapChain.CONFIG_SRGB_COLORSPACE
|
||||
flags = flags or SwapChainFlags.CONFIG_SRGB_COLORSPACE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,6 @@ function print_help {
|
||||
echo " Exclude Vulkan support from the Android build."
|
||||
echo " -s"
|
||||
echo " Add iOS simulator support to the iOS build."
|
||||
echo " -t"
|
||||
echo " Enable SwiftShader support for Vulkan in desktop builds."
|
||||
echo " -e"
|
||||
echo " Enable EGL on Linux support for desktop builds."
|
||||
echo " -l"
|
||||
@@ -61,6 +59,11 @@ function print_help {
|
||||
echo " -b"
|
||||
echo " Enable Address and Undefined Behavior Sanitizers (asan/ubsan) for debugging."
|
||||
echo " This is only for the desktop build."
|
||||
echo " -x value"
|
||||
echo " Define a preprocessor flag FILAMENT_BACKEND_DEBUG_FLAG with [value]. This is useful for"
|
||||
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 ""
|
||||
echo "Build types:"
|
||||
echo " release"
|
||||
@@ -160,8 +163,6 @@ INSTALL_COMMAND=
|
||||
VULKAN_ANDROID_OPTION="-DFILAMENT_SUPPORTS_VULKAN=ON"
|
||||
VULKAN_ANDROID_GRADLE_OPTION=""
|
||||
|
||||
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=OFF"
|
||||
|
||||
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
|
||||
|
||||
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
|
||||
@@ -172,6 +173,8 @@ MATOPT_GRADLE_OPTION=""
|
||||
|
||||
ASAN_UBSAN_OPTION=""
|
||||
|
||||
BACKEND_DEBUG_FLAG_OPTION=""
|
||||
|
||||
IOS_BUILD_SIMULATOR=false
|
||||
BUILD_UNIVERSAL_LIBRARIES=false
|
||||
|
||||
@@ -226,11 +229,11 @@ function build_desktop_target {
|
||||
-DIMPORT_EXECUTABLES_DIR=out \
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
|
||||
${SWIFTSHADER_OPTION} \
|
||||
${EGL_ON_LINUX_OPTION} \
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
${ASAN_UBSAN_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
${architectures} \
|
||||
../..
|
||||
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
|
||||
@@ -289,6 +292,7 @@ function build_webgl_with_target {
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DCMAKE_INSTALL_PREFIX="../webgl-${lc_target}/filament" \
|
||||
-DWEBGL=1 \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-webgl-${lc_target}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
@@ -363,6 +367,7 @@ function build_android_target {
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
${VULKAN_ANDROID_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
@@ -516,7 +521,7 @@ function build_android {
|
||||
if [[ "${BUILD_ANDROID_SAMPLES}" == "true" ]]; then
|
||||
for sample in ${ANDROID_SAMPLES}; do
|
||||
echo "Installing out/${sample}-debug.apk"
|
||||
cp samples/${sample}/build/outputs/apk/debug/${sample}-debug-unsigned.apk \
|
||||
cp samples/${sample}/build/outputs/apk/debug/${sample}-debug.apk \
|
||||
../out/${sample}-debug.apk
|
||||
done
|
||||
fi
|
||||
@@ -597,6 +602,7 @@ function build_ios_target {
|
||||
-DCMAKE_TOOLCHAIN_FILE=../../third_party/clang/iOS.cmake \
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
@@ -730,6 +736,13 @@ function validate_build_command {
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Make sure FILAMENT_BACKEND_DEBUG_FLAG is only meant for debug builds
|
||||
if [[ "${ISSUE_DEBUG_BUILD}" != "true" ]] && [[ ! -z "${BACKEND_DEBUG_FLAG_OPTION}" ]]; then
|
||||
echo "Error: cannot specify FILAMENT_BACKEND_DEBUG_FLAG in non-debug build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
}
|
||||
|
||||
@@ -776,7 +789,7 @@ function check_debug_release_build {
|
||||
|
||||
pushd "$(dirname "$0")" > /dev/null
|
||||
|
||||
while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
while getopts ":hacCfgijmp:q:uvslwedk:bx:" opt; do
|
||||
case ${opt} in
|
||||
h)
|
||||
print_help
|
||||
@@ -840,7 +853,7 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
echo "Platform must be one of [desktop|android|ios|webgl|all]"
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
;;
|
||||
esac
|
||||
done
|
||||
;;
|
||||
@@ -895,10 +908,6 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
IOS_BUILD_SIMULATOR=true
|
||||
echo "iOS simulator support enabled."
|
||||
;;
|
||||
t)
|
||||
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=ON"
|
||||
echo "SwiftShader support enabled."
|
||||
;;
|
||||
e)
|
||||
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=ON -DFILAMENT_SKIP_SDL2=ON -DFILAMENT_SKIP_SAMPLES=ON"
|
||||
echo "EGL on Linux support enabled; skipping SDL2."
|
||||
@@ -918,6 +927,8 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
b) ASAN_UBSAN_OPTION="-DFILAMENT_ENABLE_ASAN_UBSAN=ON"
|
||||
echo "Enabled ASAN/UBSAN"
|
||||
;;
|
||||
x) BACKEND_DEBUG_FLAG_OPTION="-DFILAMENT_BACKEND_DEBUG_FLAG=${OPTARG}"
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -${OPTARG}" >&2
|
||||
echo ""
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Build the image:
|
||||
# docker build --no-cache --tag ssfilament -f build/swiftshader/Dockerfile .
|
||||
# docker tag ssfilament ghcr.io/filament-assets/swiftshader
|
||||
#
|
||||
# Publish the image:
|
||||
# docker login ghcr.io --username <user> --password <token>
|
||||
# docker push ghcr.io/filament-assets/swiftshader
|
||||
#
|
||||
# Run the image and mount the current directory:
|
||||
# docker run -it -v `pwd`:/trees/filament -t ssfilament
|
||||
|
||||
FROM ubuntu:focal
|
||||
WORKDIR /trees
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV SWIFTSHADER_LD_LIBRARY_PATH=/trees/swiftshader/build
|
||||
ENV CXXFLAGS='-fno-builtin -Wno-pass-failed'
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get --no-install-recommends install -y \
|
||||
apt-transport-https \
|
||||
apt-utils \
|
||||
build-essential \
|
||||
cmake \
|
||||
ca-certificates \
|
||||
git \
|
||||
ninja-build \
|
||||
python \
|
||||
python3 \
|
||||
xorg-dev \
|
||||
clang-7 \
|
||||
libc++-7-dev \
|
||||
libc++abi-7-dev \
|
||||
lldb
|
||||
|
||||
# Ensure that clang is used instead of gcc.
|
||||
RUN set -eux ;\
|
||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100 ;\
|
||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100 ;\
|
||||
update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 ;\
|
||||
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||
|
||||
# Get patch files from the local Filament tree.
|
||||
COPY build/swiftshader/*.diff .
|
||||
|
||||
# Clone SwiftShader, apply patches, and build it.
|
||||
RUN set -eux ;\
|
||||
git clone https://swiftshader.googlesource.com/SwiftShader swiftshader ;\
|
||||
cd swiftshader ;\
|
||||
git checkout 139f5c3 ;\
|
||||
git apply /trees/*.diff ;\
|
||||
cd build ;\
|
||||
cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release ;\
|
||||
ninja
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
spath = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
path = Path(spath)
|
||||
|
||||
folder = "../../results/"
|
||||
|
||||
images = list(path.glob(folder + '*.png'))
|
||||
|
||||
images.sort()
|
||||
|
||||
gallery = open(path.absolute().joinpath(folder + 'index.html'), 'w')
|
||||
|
||||
gallery.write("""<html>
|
||||
<head>
|
||||
<script type="module" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.esm.js"></script>
|
||||
<script nomodule="" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.js"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@latest/dist/collection/styles/initial.css"/>
|
||||
<style>
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
margin-top: 150px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
color: blue;
|
||||
}
|
||||
a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
""")
|
||||
|
||||
tag = ''
|
||||
|
||||
for image in images:
|
||||
group = image.stem.rstrip('0123456789')
|
||||
before = f'https://filament-assets.github.io/golden/{group}/{image.name}'
|
||||
after = image.name
|
||||
gallery.write('\n')
|
||||
gallery.write(f'<h2><a href="{image.stem}.json">{image.stem}.json</a></h2>\n')
|
||||
gallery.write('<img-comparison-slider>\n')
|
||||
gallery.write(f'<img slot="before" src="{before}" /> <img slot="after" src="{after}" />\n')
|
||||
gallery.write('</img-comparison-slider>\n')
|
||||
|
||||
gallery.write("""</body>
|
||||
</html>
|
||||
""")
|
||||
@@ -1,62 +0,0 @@
|
||||
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
|
||||
index 86913ec72..3b35345af 100644
|
||||
--- a/src/Vulkan/VkPipeline.cpp
|
||||
+++ b/src/Vulkan/VkPipeline.cpp
|
||||
@@ -71,7 +71,56 @@ std::vector<uint32_t> preprocessSpirv(
|
||||
if(optimize)
|
||||
{
|
||||
// Full optimization list taken from spirv-opt.
|
||||
- opt.RegisterPerformancePasses();
|
||||
+
|
||||
+ // We have removed CreateRedundancyEliminationPass because it segfaults when encountering:
|
||||
+ // %389 = OpCompositeConstruct %7 %386 %387 %388 %86
|
||||
+ // When inserting an entry into instruction_to_value_ (which is an unordered_map)
|
||||
+ // This could perhaps be investigated further with help from asan.
|
||||
+
|
||||
+ using namespace spvtools;
|
||||
+ opt.RegisterPass(CreateWrapOpKillPass())
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateMergeReturnPass())
|
||||
+ .RegisterPass(CreateInlineExhaustivePass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreatePrivateToLocalPass())
|
||||
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
+ .RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateScalarReplacementPass())
|
||||
+ .RegisterPass(CreateLocalAccessChainConvertPass())
|
||||
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
+ .RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateLocalMultiStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateCCPPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateLoopUnrollPass(true))
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
|
||||
+ .RegisterPass(CreateCombineAccessChainsPass())
|
||||
+ .RegisterPass(CreateSimplificationPass())
|
||||
+ .RegisterPass(CreateScalarReplacementPass())
|
||||
+ .RegisterPass(CreateLocalAccessChainConvertPass())
|
||||
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
+ .RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateSSARewritePass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateVectorDCEPass())
|
||||
+ .RegisterPass(CreateDeadInsertElimPass())
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateSimplificationPass())
|
||||
+ .RegisterPass(CreateIfConversionPass())
|
||||
+ .RegisterPass(CreateCopyPropagateArraysPass())
|
||||
+ .RegisterPass(CreateReduceLoadSizePass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateBlockMergePass())
|
||||
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateBlockMergePass())
|
||||
+ .RegisterPass(CreateSimplificationPass());
|
||||
}
|
||||
|
||||
std::vector<uint32_t> optimized;
|
||||
@@ -1,127 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
function print_help {
|
||||
local self_name=$(basename "$0")
|
||||
echo "This script issues docker commands for testing Filament with SwiftShader."
|
||||
echo "The usual sequence of commands is: fetch, start, build filament release, and run."
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $self_name [command]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " build filament [debug | release]"
|
||||
echo " Use the container to build Filament."
|
||||
echo " build swiftshader [debug | release]"
|
||||
echo " Use the container to do a clean rebuild of SwiftShader."
|
||||
echo " (Note that the container already has SwiftShader built.)"
|
||||
echo " fetch"
|
||||
echo " Download the docker image from the central repository."
|
||||
echo " help"
|
||||
echo " Print this help message."
|
||||
echo " logs"
|
||||
echo " Print messages from the container's kernel ring buffer."
|
||||
echo " This is useful for diagnosing OOM issues."
|
||||
echo " run [lldb]"
|
||||
echo " Launch a test inside the container, optionally via lldb."
|
||||
echo " shell"
|
||||
echo " Interact with a bash prompt in the container."
|
||||
echo " start"
|
||||
echo " Start a container from the image."
|
||||
echo " stop"
|
||||
echo " Stop the container."
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Change the current working directory to the Filament root.
|
||||
pushd "$(dirname "$0")/../.." > /dev/null
|
||||
|
||||
if [[ "$1" == "build" ]] && [[ "$2" == "filament" ]]; then
|
||||
docker exec runner filament/build.sh -t $3 gltf_viewer
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "build" ]] && [[ "$2" == "swiftshader" ]]; then
|
||||
BUILD_TYPE="$3"
|
||||
BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}"
|
||||
docker exec --workdir /trees/swiftshader runner rm -rf build
|
||||
docker exec --workdir /trees/swiftshader runner mkdir build
|
||||
docker exec --workdir /trees/swiftshader/build runner cmake -GNinja -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ..
|
||||
docker exec --workdir /trees/swiftshader/build runner ninja
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "fetch" ]]; then
|
||||
docker pull ghcr.io/filament-assets/swiftshader:latest
|
||||
docker tag ghcr.io/filament-assets/swiftshader:latest ssfilament
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "help" ]]; then
|
||||
print_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$1" == "logs" ]]; then
|
||||
docker exec runner dmesg --human --read-clear
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "run" ]] && [[ "$2" == "lldb" ]]; then
|
||||
docker exec -i --workdir /trees/filament/results runner \
|
||||
lldb --batch -o run -o bt -- \
|
||||
../out/cmake-release/samples/gltf_viewer \
|
||||
--headless \
|
||||
--batch ../libs/viewer/tests/basic.json \
|
||||
--api vulkan
|
||||
docker exec runner /trees/filament/build/swiftshader/gallery.py
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "run" ]]; then
|
||||
docker exec --tty --workdir /trees/filament/results runner \
|
||||
/usr/bin/catchsegv \
|
||||
../out/cmake-release/samples/gltf_viewer \
|
||||
--headless \
|
||||
--batch ../libs/viewer/tests/basic.json \
|
||||
--api vulkan
|
||||
docker exec runner /trees/filament/build/swiftshader/gallery.py
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "shell" ]]; then
|
||||
docker exec --interactive --tty runner /bin/bash
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Notes on options being passed to docker's run command:
|
||||
#
|
||||
# - The memory constraint seems to prevent an OOM signal in GitHub Actions.
|
||||
# - The cap / security args allow use of lldb and creation of core dumps.
|
||||
# - The privileged arg allows use of dmesg for examining OOM logs.
|
||||
#
|
||||
# Currently, a GitHub Actions VM has 2 CPUs, 7 GB RAM, and 14 GB of SSD disk space.
|
||||
#
|
||||
# Please be aware that Docker Desktop might impose additional resource constraints, and that those
|
||||
# settings can only be controlled with its GUI. We recommend at least 7 GB of memory and 2 GB swap.
|
||||
if [[ "$1" == "start" ]]; then
|
||||
mkdir -p results
|
||||
docker run --tty --rm --detach --privileged \
|
||||
--memory 6.5g \
|
||||
--name runner \
|
||||
--cap-add=SYS_PTRACE \
|
||||
--security-opt seccomp=unconfined \
|
||||
--security-opt apparmor=unconfined \
|
||||
--volume `pwd`:/trees/filament \
|
||||
--workdir /trees \
|
||||
ssfilament
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "stop" ]]; then
|
||||
docker container rm runner --force
|
||||
exit $?
|
||||
fi
|
||||
|
||||
print_help
|
||||
exit 1
|
||||
+2
-2
@@ -476,7 +476,7 @@ Energy conservation is one of the key components of a good BRDF for physically b
|
||||
<p>
|
||||
|
||||
|
||||
For the specular term, \(f_m\) is a mirror BRDF that can be modeled with the Fresnel law, noted \(F\) in the Cook-Torrance approximation of the microfacet model integration:
|
||||
For the specular term, \(f_r\) is a mirror BRDF that can be modeled with the Fresnel law, noted \(F\) in the Cook-Torrance approximation of the microfacet model integration:
|
||||
|
||||
</p><p>
|
||||
|
||||
@@ -857,7 +857,7 @@ The full GLSL implementation of the standard model is shown in <a href="#listing
|
||||
<span class="line"> <span class="hljs-comment">// perceptually linear roughness to roughness (see parameterization)</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> roughness = perceptualRoughness * perceptualRoughness;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, a);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, roughness);</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> F = F_Schlick(LoH, f0);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> V = V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
|
||||
<span class="line"></span>
|
||||
|
||||
+141
-53
File diff suppressed because one or more lines are too long
+33
-1
@@ -1397,7 +1397,7 @@ Type
|
||||
: `string`
|
||||
|
||||
Value
|
||||
: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`. Defaults to `opaque`.
|
||||
: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`, `custom`. Defaults to `opaque`.
|
||||
|
||||
Description
|
||||
: Defines how/if the rendered object is blended with the content of the render target.
|
||||
@@ -1420,6 +1420,7 @@ Description
|
||||
of the material's output defines whether a fragment is discarded or not. Additionally,
|
||||
ALPHA_TO_COVERAGE is enabled for non-translucent views. See the maskThreshold section for more
|
||||
information.
|
||||
- **Custom**: blending is enabled. But the blending function is user specified. See `blendFunction`.
|
||||
|
||||
!!! Note
|
||||
When `blending` is set to `masked`, alpha to coverage is automatically enabled for the material.
|
||||
@@ -1432,6 +1433,36 @@ material {
|
||||
}
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
### Blending and transparency: blendFunction
|
||||
|
||||
Type
|
||||
: `object`
|
||||
|
||||
Fields
|
||||
: `srcRGB`, `srcA`, `dstRGB`, `dstA`
|
||||
|
||||
Description
|
||||
: - *srcRGB*: source function applied to the RGB channels
|
||||
- *srcA*: source function applied to the alpha channel
|
||||
- *srcRGB*: destination function applied to the RGB channels
|
||||
- *srcRGB*: destination function applied to the alpha channel
|
||||
The values possible for each functions are one of `zero`, `one`, `srcColor`, `oneMinusSrcColor`,
|
||||
`dstColor`, `oneMinusDstColor`, `srcAlpha`, `oneMinusSrcAlpha`, `dstAlpha`,
|
||||
`oneMinusDstAlpha`, `srcAlphaSaturate`
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
blending : custom,
|
||||
blendFunction :
|
||||
{
|
||||
srcRGB: one,
|
||||
srcA: one,
|
||||
dstRGB: oneMinusSrcColor,
|
||||
dstA: oneMinusSrcAlpha
|
||||
}
|
||||
}
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
### Blending and transparency: postLightingBlending
|
||||
|
||||
Type
|
||||
@@ -2394,6 +2425,7 @@ The following APIs are only available from the vertex block:
|
||||
**getWorldFromModelMatrix()** | float4x4 | Matrix that converts from model (object) space to world space
|
||||
**getWorldFromModelNormalMatrix()** | float3x3 | Matrix that converts normals from model (object) space to world space
|
||||
**getVertexIndex()** | int | Index of the current vertex
|
||||
**getEyeIndex()** | int | Index of the eye being rendered, starting at 0
|
||||
|
||||
### Fragment only
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -43,7 +43,7 @@ filament-viewer::part(canvas) {
|
||||
</p>
|
||||
|
||||
</main>
|
||||
<script src="https://unpkg.com/filament@1.44.0/filament.js"></script>
|
||||
<script src="https://unpkg.com/filament@1.51.6/filament.js"></script>
|
||||
<script src="https://unpkg.com/gltumble"></script>
|
||||
<script src="filament-viewer.js" type="module"></script>
|
||||
</body>
|
||||
|
||||
@@ -163,6 +163,7 @@ set(PRIVATE_HDRS
|
||||
src/ResourceList.h
|
||||
src/ShadowMap.h
|
||||
src/ShadowMapManager.h
|
||||
src/SharedHandle.h
|
||||
src/TypedUniformBuffer.h
|
||||
src/UniformBuffer.h
|
||||
src/components/CameraManager.h
|
||||
@@ -214,6 +215,7 @@ set(PRIVATE_HDRS
|
||||
set(MATERIAL_SRCS
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/blitDepth.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/blitArray.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
|
||||
@@ -66,11 +66,13 @@ set(PRIVATE_HDRS
|
||||
# OpenGL / OpenGL ES Sources
|
||||
# ==================================================================================================
|
||||
|
||||
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
|
||||
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/OpenGLPlatform.h
|
||||
src/opengl/gl_headers.cpp
|
||||
src/opengl/gl_headers.h
|
||||
src/opengl/GLBufferObject.h
|
||||
src/opengl/GLTexture.h
|
||||
src/opengl/GLUtils.cpp
|
||||
src/opengl/GLUtils.h
|
||||
src/opengl/OpenGLBlobCache.cpp
|
||||
@@ -166,8 +168,10 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatform.h
|
||||
src/vulkan/caching/VulkanDescriptorSet.cpp
|
||||
src/vulkan/caching/VulkanDescriptorSet.h
|
||||
src/vulkan/caching/VulkanDescriptorSetManager.cpp
|
||||
src/vulkan/caching/VulkanDescriptorSetManager.h
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
@@ -413,6 +417,7 @@ if (APPLE OR LINUX)
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
@@ -474,6 +479,10 @@ if (APPLE)
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -24,12 +24,14 @@
|
||||
|
||||
#include <backend/PresentCallable.h>
|
||||
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <array> // FIXME: STL headers are not allowed in public headers
|
||||
#include <type_traits> // FIXME: STL headers are not allowed in public headers
|
||||
#include <variant> // FIXME: STL headers are not allowed in public headers
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -90,12 +92,15 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON
|
||||
*/
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40;
|
||||
|
||||
|
||||
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3.
|
||||
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
|
||||
static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES.
|
||||
|
||||
static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte
|
||||
// of push constant (we assume 4-byte
|
||||
// types).
|
||||
|
||||
// Per feature level caps
|
||||
// Use (int)FeatureLevel to index this array
|
||||
static constexpr struct {
|
||||
@@ -158,14 +163,16 @@ static constexpr const char* backendToString(Backend backend) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the shader language. Similar to the above backend enum, but the OpenGL backend can select
|
||||
* between two shader languages: ESSL 1.0 and ESSL 3.0.
|
||||
* Defines the shader language. Similar to the above backend enum, with some differences:
|
||||
* - The OpenGL backend can select between two shader languages: ESSL 1.0 and ESSL 3.0.
|
||||
* - The Metal backend can prefer precompiled Metal libraries, while falling back to MSL.
|
||||
*/
|
||||
enum class ShaderLanguage {
|
||||
ESSL1 = 0,
|
||||
ESSL3 = 1,
|
||||
SPIRV = 2,
|
||||
MSL = 3,
|
||||
METAL_LIBRARY = 4,
|
||||
};
|
||||
|
||||
static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) {
|
||||
@@ -178,6 +185,8 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag
|
||||
return "SPIR-V";
|
||||
case ShaderLanguage::MSL:
|
||||
return "MSL";
|
||||
case ShaderLanguage::METAL_LIBRARY:
|
||||
return "Metal precompiled library";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +336,7 @@ enum class UniformType : uint8_t {
|
||||
/**
|
||||
* Supported constant parameter types
|
||||
*/
|
||||
enum class ConstantType : uint8_t {
|
||||
enum class ConstantType : uint8_t {
|
||||
INT,
|
||||
FLOAT,
|
||||
BOOL
|
||||
@@ -682,7 +691,7 @@ enum class TextureUsage : uint16_t {
|
||||
SUBPASS_INPUT = 0x0020, //!< Texture can be used as a subpass input
|
||||
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 the destination of a blit()
|
||||
PROTECTED = 0x0100, //!< Texture can be used for protected content
|
||||
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
|
||||
};
|
||||
|
||||
@@ -1214,13 +1223,15 @@ struct StencilState {
|
||||
uint8_t padding = 0;
|
||||
};
|
||||
|
||||
using PushConstantVariant = std::variant<int32_t, float, bool>;
|
||||
|
||||
static_assert(sizeof(StencilState::StencilOperations) == 5u,
|
||||
"StencilOperations size not what was intended");
|
||||
|
||||
static_assert(sizeof(StencilState) == 12u,
|
||||
"StencilState size not what was intended");
|
||||
|
||||
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
||||
using FrameScheduledCallback = utils::Invocable<void(backend::PresentCallable)>;
|
||||
|
||||
enum class Workaround : uint16_t {
|
||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||
@@ -1237,9 +1248,6 @@ enum class Workaround : uint16_t {
|
||||
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
|
||||
// Multiple workarounds needed for PowerVR GPUs
|
||||
POWER_VR_SHADER_WORKAROUNDS,
|
||||
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
|
||||
// performance more if we end-up pinned on the same one.
|
||||
DISABLE_THREAD_AFFINITY
|
||||
};
|
||||
|
||||
//! The type of technique for stereoscopic rendering
|
||||
|
||||
@@ -75,6 +75,19 @@ protected:
|
||||
HandleBase(HandleBase const& rhs) noexcept = default;
|
||||
HandleBase& operator=(HandleBase const& rhs) noexcept = default;
|
||||
|
||||
HandleBase(HandleBase&& rhs) noexcept
|
||||
: object(rhs.object) {
|
||||
rhs.object = nullid;
|
||||
}
|
||||
|
||||
HandleBase& operator=(HandleBase&& rhs) noexcept {
|
||||
if (this != &rhs) {
|
||||
object = rhs.object;
|
||||
rhs.object = nullid;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
HandleId object;
|
||||
};
|
||||
@@ -89,8 +102,10 @@ struct Handle : public HandleBase {
|
||||
Handle() noexcept = default;
|
||||
|
||||
Handle(Handle const& rhs) noexcept = default;
|
||||
Handle(Handle&& rhs) noexcept = default;
|
||||
|
||||
Handle& operator=(Handle const& rhs) noexcept = default;
|
||||
Handle& operator=(Handle&& rhs) noexcept = default;
|
||||
|
||||
explicit Handle(HandleId id) noexcept : HandleBase(id) { }
|
||||
|
||||
|
||||
@@ -65,6 +65,12 @@ public:
|
||||
* Disable backend handles use-after-free checks.
|
||||
*/
|
||||
bool disableHandleUseAfterFreeCheck = false;
|
||||
|
||||
/**
|
||||
* Force GLES2 context if supported, or pretend the context is ES2. Only meaningful on
|
||||
* GLES 3.x backends.
|
||||
*/
|
||||
bool forceGLES2Context = false;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace filament::backend {
|
||||
* and optional user data:
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* swapChain->setFrameScheduledCallback(myFrameScheduledCallback, nullptr);
|
||||
* swapChain->setFrameScheduledCallback(nullptr, myFrameScheduledCallback);
|
||||
* if (renderer->beginFrame(swapChain)) {
|
||||
* renderer->render(view);
|
||||
* renderer->endFrame();
|
||||
@@ -58,8 +58,6 @@ namespace filament::backend {
|
||||
* @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other
|
||||
* backends ignore the callback (which will never be called) and proceed normally.
|
||||
*
|
||||
* @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread.
|
||||
*
|
||||
* Applications *must* call each PresentCallable they receive. Each PresentCallable represents a
|
||||
* frame that is waiting to be presented. If an application fails to call a PresentCallable, a
|
||||
* memory leak could occur. To "cancel" the presentation of a frame, pass false to the
|
||||
|
||||
@@ -84,6 +84,9 @@ public:
|
||||
// null terminating character.
|
||||
Program& shader(ShaderStage shader, void const* data, size_t size);
|
||||
|
||||
// sets the language of the shader sources provided with shader() (defaults to ESSL3)
|
||||
Program& shaderLanguage(ShaderLanguage shaderLanguage);
|
||||
|
||||
// Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is
|
||||
// not permitted in glsl. The backend needs a way to associate a uniform block
|
||||
// to a binding point.
|
||||
@@ -114,8 +117,18 @@ public:
|
||||
Program& specializationConstants(
|
||||
utils::FixedCapacityVector<SpecializationConstant> specConstants) noexcept;
|
||||
|
||||
struct PushConstant {
|
||||
utils::CString name;
|
||||
ConstantType type;
|
||||
};
|
||||
|
||||
Program& pushConstants(ShaderStage stage,
|
||||
utils::FixedCapacityVector<PushConstant> constants) noexcept;
|
||||
|
||||
Program& cacheId(uint64_t cacheId) noexcept;
|
||||
|
||||
Program& multiview(bool multiview) noexcept;
|
||||
|
||||
ShaderSource const& getShadersSource() const noexcept { return mShadersSource; }
|
||||
ShaderSource& getShadersSource() noexcept { return mShadersSource; }
|
||||
|
||||
@@ -134,6 +147,8 @@ public:
|
||||
utils::CString const& getName() const noexcept { return mName; }
|
||||
utils::CString& getName() noexcept { return mName; }
|
||||
|
||||
auto const& getShaderLanguage() const { return mShaderLanguage; }
|
||||
|
||||
utils::FixedCapacityVector<SpecializationConstant> const& getSpecializationConstants() const noexcept {
|
||||
return mSpecializationConstants;
|
||||
}
|
||||
@@ -141,8 +156,19 @@ public:
|
||||
return mSpecializationConstants;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<PushConstant> const& getPushConstants(
|
||||
ShaderStage stage) const noexcept {
|
||||
return mPushConstants[static_cast<uint8_t>(stage)];
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<PushConstant>& getPushConstants(ShaderStage stage) noexcept {
|
||||
return mPushConstants[static_cast<uint8_t>(stage)];
|
||||
}
|
||||
|
||||
uint64_t getCacheId() const noexcept { return mCacheId; }
|
||||
|
||||
bool isMultiview() const noexcept { return mMultiview; }
|
||||
|
||||
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
|
||||
|
||||
private:
|
||||
@@ -151,13 +177,20 @@ private:
|
||||
UniformBlockInfo mUniformBlocks = {};
|
||||
SamplerGroupInfo mSamplerGroups = {};
|
||||
ShaderSource mShadersSource;
|
||||
ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3;
|
||||
utils::CString mName;
|
||||
uint64_t mCacheId{};
|
||||
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
|
||||
utils::FixedCapacityVector<SpecializationConstant> mSpecializationConstants;
|
||||
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> mAttributes;
|
||||
std::array<UniformInfo, Program::UNIFORM_BINDING_COUNT> mBindingUniformInfo;
|
||||
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
|
||||
// Indicates the current engine was initialized with multiview stereo, and the variant for this
|
||||
// program contains STE flag. This will be referred later for the OpenGL shader compiler to
|
||||
// determine whether shader code replacement for the num_views should be performed.
|
||||
// This variable could be promoted as a more generic variable later if other similar needs occur.
|
||||
bool mMultiview = false;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -29,17 +29,36 @@ namespace filament::backend {
|
||||
//! \privatesection
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level) noexcept
|
||||
: handle(handle), level(level) {
|
||||
}
|
||||
|
||||
TargetBufferInfo(Handle<HwTexture> handle) noexcept // NOLINT(*-explicit-constructor)
|
||||
: handle(handle) {
|
||||
}
|
||||
|
||||
TargetBufferInfo() noexcept = default;
|
||||
|
||||
// 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
|
||||
// Starting layer index for multiview. This value is only used when the `layerCount` for the
|
||||
// render target is greater than 1.
|
||||
uint8_t baseViewIndex = 0;
|
||||
|
||||
// level to be used
|
||||
uint8_t level = 0;
|
||||
|
||||
// for cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
|
||||
// For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
|
||||
uint16_t layer = 0;
|
||||
};
|
||||
|
||||
@@ -64,7 +83,7 @@ public:
|
||||
|
||||
MRT() noexcept = default;
|
||||
|
||||
MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions)
|
||||
MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions, *-explicit-constructor)
|
||||
: mInfos{ color } {
|
||||
}
|
||||
|
||||
@@ -84,7 +103,7 @@ public:
|
||||
|
||||
// this is here for backward compatibility
|
||||
MRT(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
|
||||
: mInfos{{ handle, 0, level, layer }} {
|
||||
: mInfos{{ handle, level, layer, 0 }} {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -140,6 +140,23 @@ public:
|
||||
*/
|
||||
virtual uint32_t getDefaultFramebufferObject() noexcept;
|
||||
|
||||
/**
|
||||
* Called by the backend when a frame starts.
|
||||
* @param steady_clock_ns vsync time point on the monotonic clock
|
||||
* @param refreshIntervalNs refresh interval in nanosecond
|
||||
* @param frameId a frame id
|
||||
*/
|
||||
virtual void beginFrame(
|
||||
int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs,
|
||||
uint32_t frameId) noexcept;
|
||||
|
||||
/**
|
||||
* Called by the backend when a frame ends.
|
||||
* @param frameId the frame id used in beginFrame
|
||||
*/
|
||||
virtual void endFrame(
|
||||
uint32_t frameId) noexcept;
|
||||
|
||||
/**
|
||||
* Type of contexts available
|
||||
@@ -191,6 +208,12 @@ public:
|
||||
utils::Invocable<void()> preContextChange,
|
||||
utils::Invocable<void(size_t index)> postContextChange) noexcept;
|
||||
|
||||
/**
|
||||
* Called by the backend just before calling commit()
|
||||
* @see commit()
|
||||
*/
|
||||
virtual void preCommit() noexcept;
|
||||
|
||||
/**
|
||||
* Called by the driver once the current frame finishes drawing. Typically, this should present
|
||||
* the drawSwapChain. This is for example where `eglMakeCurrent()` would be called.
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
|
||||
#include <utils/android/PerformanceHintManager.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -58,6 +62,13 @@ protected:
|
||||
|
||||
void terminate() noexcept override;
|
||||
|
||||
void beginFrame(
|
||||
int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs,
|
||||
uint32_t frameId) noexcept override;
|
||||
|
||||
void preCommit() noexcept override;
|
||||
|
||||
/**
|
||||
* Set the presentation time using `eglPresentationTimeANDROID`
|
||||
* @param presentationTimeInNanosecond
|
||||
@@ -81,6 +92,11 @@ protected:
|
||||
private:
|
||||
int mOSVersion;
|
||||
ExternalStreamManagerAndroid& mExternalStreamManager;
|
||||
utils::PerformanceHintManager mPerformanceHintManager;
|
||||
utils::PerformanceHintManager::Session mPerformanceHintSession;
|
||||
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
clock::time_point mStartTimeOfActualWork;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -47,6 +47,14 @@ struct VulkanPlatformPrivate;
|
||||
class VulkanPlatform : public Platform, utils::PrivateImplementation<VulkanPlatformPrivate> {
|
||||
public:
|
||||
|
||||
struct ExtensionHashFn {
|
||||
std::size_t operator()(utils::CString const& s) const noexcept {
|
||||
return std::hash<std::string>{}(s.data());
|
||||
}
|
||||
};
|
||||
// Utility for managing device or instance extensions during initialization.
|
||||
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn>;
|
||||
|
||||
/**
|
||||
* A collection of handles to objects and metadata that comprises a Vulkan context. The client
|
||||
* can instantiate this struct and pass to Engine::Builder::sharedContext if they wishes to
|
||||
@@ -192,6 +200,13 @@ public:
|
||||
virtual SwapChainPtr createSwapChain(void* nativeWindow, uint64_t flags = 0,
|
||||
VkExtent2D extent = {0, 0});
|
||||
|
||||
/**
|
||||
* Allows implementers to provide instance extensions that they'd like to include in the
|
||||
* instance creation.
|
||||
* @return A set of extensions to enable for the instance.
|
||||
*/
|
||||
virtual ExtensionSet getRequiredInstanceExtensions() { return {}; }
|
||||
|
||||
/**
|
||||
* Destroy the swapchain.
|
||||
* @param handle The handle returned by createSwapChain()
|
||||
@@ -236,10 +251,9 @@ public:
|
||||
VkQueue getGraphicsQueue() const noexcept;
|
||||
|
||||
private:
|
||||
// Platform dependent helper methods
|
||||
using ExtensionSet = std::unordered_set<std::string_view>;
|
||||
static ExtensionSet getRequiredInstanceExtensions();
|
||||
static ExtensionSet getSwapchainInstanceExtensions();
|
||||
|
||||
// Platform dependent helper methods
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
void requestExit();
|
||||
|
||||
// suspend or unsuspend the queue.
|
||||
bool isPaused() const noexcept;
|
||||
void setPaused(bool paused);
|
||||
|
||||
bool isExitRequested() const;
|
||||
|
||||
@@ -134,7 +134,7 @@ struct CommandType<void (Driver::*)(ARGS...)> {
|
||||
|
||||
public:
|
||||
template<typename M, typename D>
|
||||
static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) noexcept {
|
||||
static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) {
|
||||
Command* self = static_cast<Command*>(base);
|
||||
*next = align(sizeof(Command));
|
||||
#if DEBUG_COMMAND_STREAM
|
||||
@@ -168,7 +168,7 @@ struct CommandType<void (Driver::*)(ARGS...)> {
|
||||
|
||||
class CustomCommand : public CommandBase {
|
||||
std::function<void()> mCommand;
|
||||
static void execute(Driver&, CommandBase* base, intptr_t* next) noexcept;
|
||||
static void execute(Driver&, CommandBase* base, intptr_t* next);
|
||||
public:
|
||||
inline CustomCommand(CustomCommand&& rhs) = default;
|
||||
inline explicit CustomCommand(std::function<void()> cmd)
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
// the fn function will execute a batch of driver commands
|
||||
// this gives the driver a chance to wrap their execution in a meaningful manner
|
||||
// the default implementation simply calls fn
|
||||
virtual void execute(std::function<void(void)> const& fn) noexcept;
|
||||
virtual void execute(std::function<void(void)> const& fn);
|
||||
|
||||
// This is called on debug build, or when enabled manually on the backend thread side.
|
||||
virtual void debugCommandBegin(CommandStream* cmds,
|
||||
|
||||
@@ -133,18 +133,18 @@ DECL_DRIVER_API_0(tick)
|
||||
|
||||
DECL_DRIVER_API_N(beginFrame,
|
||||
int64_t, monotonic_clock_ns,
|
||||
int64_t, refreshIntervalNs,
|
||||
uint32_t, frameId)
|
||||
|
||||
DECL_DRIVER_API_N(setFrameScheduledCallback,
|
||||
backend::SwapChainHandle, sch,
|
||||
backend::FrameScheduledCallback, callback,
|
||||
void*, user)
|
||||
backend::CallbackHandler*, handler,
|
||||
backend::FrameScheduledCallback&&, callback)
|
||||
|
||||
DECL_DRIVER_API_N(setFrameCompletedCallback,
|
||||
backend::SwapChainHandle, sch,
|
||||
backend::CallbackHandler*, handler,
|
||||
backend::CallbackHandler::Callback, callback,
|
||||
void*, user)
|
||||
utils::Invocable<void(void)>&&, callback)
|
||||
|
||||
DECL_DRIVER_API_N(setPresentationTime,
|
||||
int64_t, monotonic_clock_ns)
|
||||
@@ -303,6 +303,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
@@ -432,6 +433,11 @@ DECL_DRIVER_API_N(bindSamplers,
|
||||
uint32_t, index,
|
||||
backend::SamplerGroupHandle, sbh)
|
||||
|
||||
DECL_DRIVER_API_N(setPushConstant,
|
||||
backend::ShaderStage, stage,
|
||||
uint8_t, index,
|
||||
backend::PushConstantVariant, value)
|
||||
|
||||
DECL_DRIVER_API_N(insertEventMarker,
|
||||
const char*, string,
|
||||
uint32_t, len = 0)
|
||||
@@ -495,7 +501,7 @@ DECL_DRIVER_API_N(blit,
|
||||
math::uint2, size)
|
||||
|
||||
DECL_DRIVER_API_N(bindPipeline,
|
||||
backend::PipelineState, state)
|
||||
backend::PipelineState const&, state)
|
||||
|
||||
DECL_DRIVER_API_N(bindRenderPrimitive,
|
||||
backend::RenderPrimitiveHandle, rph)
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
*
|
||||
*/
|
||||
template<typename D, typename ... ARGS>
|
||||
Handle<D> allocateAndConstruct(ARGS&& ... args) noexcept {
|
||||
Handle<D> allocateAndConstruct(ARGS&& ... args) {
|
||||
Handle<D> h{ allocateHandle<D>() };
|
||||
D* addr = handle_cast<D*>(h);
|
||||
new(addr) D(std::forward<ARGS>(args)...);
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
*/
|
||||
template<typename D, typename B, typename ... ARGS>
|
||||
typename std::enable_if_t<std::is_base_of_v<B, D>, D>*
|
||||
destroyAndConstruct(Handle<B> const& handle, ARGS&& ... args) noexcept {
|
||||
destroyAndConstruct(Handle<B> const& handle, ARGS&& ... args) {
|
||||
assert_invariant(handle);
|
||||
D* addr = handle_cast<D*>(const_cast<Handle<B>&>(handle));
|
||||
assert_invariant(addr);
|
||||
@@ -163,7 +163,7 @@ public:
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B>& handle) noexcept {
|
||||
handle_cast(Handle<B>& handle) {
|
||||
assert_invariant(handle);
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
|
||||
@@ -185,7 +185,7 @@ public:
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B> const& handle) noexcept {
|
||||
handle_cast(Handle<B> const& handle) {
|
||||
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ private:
|
||||
return (id & HANDLE_HEAP_FLAG) == 0u;
|
||||
}
|
||||
|
||||
HandleBase::HandleId allocateHandleSlow(size_t size) noexcept;
|
||||
HandleBase::HandleId allocateHandleSlow(size_t size);
|
||||
void deallocateHandleSlow(HandleBase::HandleId id, size_t size) noexcept;
|
||||
|
||||
// We inline this because it's just 4 instructions in the fast case
|
||||
|
||||
@@ -57,7 +57,13 @@ void CommandBufferQueue::requestExit() {
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
bool CommandBufferQueue::isPaused() const noexcept {
|
||||
std::lock_guard<utils::Mutex> const lock(mLock);
|
||||
return mPaused;
|
||||
}
|
||||
|
||||
void CommandBufferQueue::setPaused(bool paused) {
|
||||
std::lock_guard<utils::Mutex> const lock(mLock);
|
||||
if (paused) {
|
||||
mPaused = true;
|
||||
} else {
|
||||
@@ -125,6 +131,9 @@ void CommandBufferQueue::flush() noexcept {
|
||||
#endif
|
||||
|
||||
SYSTRACE_NAME("waiting: CircularBuffer::flush()");
|
||||
ASSERT_POSTCONDITION(!mPaused,
|
||||
"CommandStream is full, but since the rendering thread is paused, "
|
||||
"the buffer cannot flush and we will deadlock. Instead, abort.");
|
||||
mCondition.wait(lock, [this, requiredSize]() -> bool {
|
||||
// TODO: on macOS, we need to call pumpEvents from time to time
|
||||
return mFreeSpace >= requiredSize;
|
||||
|
||||
@@ -149,7 +149,7 @@ void CommandType<void (Driver::*)(ARGS...)>::Command<METHOD>::log() noexcept {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) noexcept {
|
||||
void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) {
|
||||
*next = CustomCommand::align(sizeof(CustomCommand));
|
||||
static_cast<CustomCommand*>(base)->mCommand();
|
||||
static_cast<CustomCommand*>(base)->~CustomCommand();
|
||||
|
||||
@@ -214,7 +214,7 @@ size_t Driver::getElementTypeSize(ElementType type) noexcept {
|
||||
|
||||
Driver::~Driver() noexcept = default;
|
||||
|
||||
void Driver::execute(std::function<void(void)> const& fn) noexcept {
|
||||
void Driver::execute(std::function<void(void)> const& fn) {
|
||||
fn();
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ void* HandleAllocator<P0, P1, P2>::handleToPointerSlow(HandleBase::HandleId id)
|
||||
}
|
||||
|
||||
template <size_t P0, size_t P1, size_t P2>
|
||||
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) noexcept {
|
||||
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) {
|
||||
void* p = ::malloc(size);
|
||||
std::unique_lock lock(mLock);
|
||||
|
||||
|
||||
@@ -29,21 +29,21 @@
|
||||
#include "backend/platforms/PlatformCocoaTouchGL.h"
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include <backend/platforms/PlatformCocoaGL.h>
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(FILAMENT_SUPPORTS_X11)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformGLX.h"
|
||||
#endif
|
||||
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformEGLHeadless.h"
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformWGL.h"
|
||||
#endif
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
@@ -111,8 +111,7 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
}
|
||||
assert_invariant(*backend == Backend::OPENGL);
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL)
|
||||
#if defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(FILAMENT_USE_SWIFTSHADER)
|
||||
// Swiftshader OpenGLES support is deprecated and incomplete
|
||||
#if defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
return nullptr;
|
||||
#elif defined(__ANDROID__)
|
||||
return new PlatformEGLAndroid();
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace filament::backend {
|
||||
using namespace utils;
|
||||
|
||||
// We want these in the .cpp file, so they're not inlined (not worth it)
|
||||
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
|
||||
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
|
||||
}
|
||||
|
||||
Program::Program(Program&& rhs) noexcept = default;
|
||||
@@ -47,6 +47,11 @@ Program& Program::shader(ShaderStage shader, void const* data, size_t size) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) {
|
||||
mShaderLanguage = shaderLanguage;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::uniformBlockBindings(
|
||||
FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept {
|
||||
for (auto const& item : uniformBlockBindings) {
|
||||
@@ -86,11 +91,22 @@ Program& Program::specializationConstants(
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::pushConstants(ShaderStage stage,
|
||||
utils::FixedCapacityVector<PushConstant> constants) noexcept {
|
||||
mPushConstants[static_cast<uint8_t>(stage)] = std::move(constants);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::cacheId(uint64_t cacheId) noexcept {
|
||||
mCacheId = cacheId;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::multiview(bool multiview) noexcept {
|
||||
mMultiview = multiview;
|
||||
return *this;
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, const Program& builder) {
|
||||
out << "Program{";
|
||||
builder.mLogger(out);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define TNT_FILAMENT_DRIVER_METALBUFFER_H
|
||||
|
||||
#include "MetalContext.h"
|
||||
#include "MetalPlatform.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
@@ -28,47 +29,122 @@
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class TrackedMetalBuffer {
|
||||
class ScopedAllocationTimer {
|
||||
public:
|
||||
TrackedMetalBuffer() noexcept : mBuffer(nil) {}
|
||||
TrackedMetalBuffer(id<MTLBuffer> buffer) noexcept : mBuffer(buffer) {
|
||||
if (buffer) {
|
||||
aliveBuffers++;
|
||||
}
|
||||
}
|
||||
~TrackedMetalBuffer() {
|
||||
if (mBuffer) {
|
||||
aliveBuffers--;
|
||||
ScopedAllocationTimer(const char* name) : mBeginning(clock_t::now()), mName(name) {}
|
||||
~ScopedAllocationTimer() {
|
||||
using namespace std::literals::chrono_literals;
|
||||
static constexpr std::chrono::seconds LONG_TIME_THRESHOLD = 10s;
|
||||
|
||||
auto end = clock_t::now();
|
||||
std::chrono::duration<double, std::micro> allocationTimeMicroseconds = end - mBeginning;
|
||||
|
||||
if (UTILS_UNLIKELY(allocationTimeMicroseconds > LONG_TIME_THRESHOLD)) {
|
||||
if (platform && platform->hasDebugUpdateStatFunc()) {
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "filament.metal.long_buffer_allocation_time.%s",
|
||||
mName);
|
||||
platform->debugUpdateStat(
|
||||
buffer, static_cast<uint64_t>(allocationTimeMicroseconds.count()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrackedMetalBuffer(TrackedMetalBuffer&&) = delete;
|
||||
TrackedMetalBuffer(TrackedMetalBuffer const&) = delete;
|
||||
TrackedMetalBuffer& operator=(TrackedMetalBuffer const&) = delete;
|
||||
|
||||
TrackedMetalBuffer& operator=(TrackedMetalBuffer&& rhs) noexcept {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
id<MTLBuffer> get() const noexcept { return mBuffer; }
|
||||
operator bool() const noexcept { return bool(mBuffer); }
|
||||
|
||||
static uint64_t getAliveBuffers() { return aliveBuffers; }
|
||||
static void setPlatform(MetalPlatform* p) { platform = p; }
|
||||
|
||||
private:
|
||||
void swap(TrackedMetalBuffer& other) noexcept {
|
||||
id<MTLBuffer> temp = mBuffer;
|
||||
mBuffer = other.mBuffer;
|
||||
other.mBuffer = temp;
|
||||
typedef std::chrono::steady_clock clock_t;
|
||||
|
||||
static MetalPlatform* platform;
|
||||
|
||||
std::chrono::time_point<clock_t> mBeginning;
|
||||
const char* mName;
|
||||
};
|
||||
|
||||
#ifndef FILAMENT_METAL_BUFFER_TRACKING
|
||||
#define FILAMENT_METAL_BUFFER_TRACKING 0
|
||||
#endif
|
||||
|
||||
class MetalBufferTracking {
|
||||
public:
|
||||
static constexpr size_t EXCESS_BUFFER_COUNT = 30000;
|
||||
|
||||
enum class Type {
|
||||
NONE = 0,
|
||||
GENERIC = 1,
|
||||
RING = 2,
|
||||
STAGING = 3,
|
||||
};
|
||||
static constexpr size_t TypeCount = 3;
|
||||
|
||||
static constexpr auto toIndex(Type t) {
|
||||
assert_invariant(t != Type::NONE);
|
||||
switch (t) {
|
||||
case Type::NONE:
|
||||
case Type::GENERIC:
|
||||
return 0;
|
||||
case Type::RING:
|
||||
return 1;
|
||||
case Type::STAGING:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLBuffer> mBuffer;
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
static void initialize() {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
for (size_t i = 0; i < TypeCount; i++) {
|
||||
aliveBuffers[i] = [NSHashTable weakObjectsHashTable];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static std::atomic<uint64_t> aliveBuffers;
|
||||
static void setPlatform(MetalPlatform* p) { platform = p; }
|
||||
|
||||
static void track(id<MTLBuffer> buffer, Type type) {
|
||||
assert_invariant(type != Type::NONE);
|
||||
if (UTILS_UNLIKELY(getAliveBuffers() >= EXCESS_BUFFER_COUNT)) {
|
||||
if (platform && platform->hasDebugUpdateStatFunc()) {
|
||||
platform->debugUpdateStat("filament.metal.excess_buffers_allocated",
|
||||
MetalBufferTracking::getAliveBuffers());
|
||||
}
|
||||
}
|
||||
[aliveBuffers[toIndex(type)] addObject:buffer];
|
||||
}
|
||||
|
||||
static uint64_t getAliveBuffers() {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 1; i < TypeCount; i++) {
|
||||
sum += getAliveBuffers(static_cast<Type>(i));
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
static uint64_t getAliveBuffers(Type type) {
|
||||
assert_invariant(type != Type::NONE);
|
||||
NSHashTable* hashTable = aliveBuffers[toIndex(type)];
|
||||
// Caution! We can't simply use hashTable.count here, which is inaccurate.
|
||||
// See http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/
|
||||
return hashTable.objectEnumerator.allObjects.count;
|
||||
}
|
||||
#else
|
||||
static void initialize() {}
|
||||
static void setPlatform(MetalPlatform* p) {}
|
||||
static id<MTLBuffer> track(id<MTLBuffer> buffer, Type type) { return buffer; }
|
||||
static uint64_t getAliveBuffers() { return 0; }
|
||||
static uint64_t getAliveBuffers(Type type) { return 0; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
static std::array<NSHashTable<id<MTLBuffer>>*, TypeCount> aliveBuffers;
|
||||
static MetalPlatform* platform;
|
||||
#endif
|
||||
};
|
||||
|
||||
class MetalBuffer {
|
||||
@@ -122,7 +198,7 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
TrackedMetalBuffer mBuffer;
|
||||
id<MTLBuffer> mBuffer;
|
||||
size_t mBufferSize = 0;
|
||||
void* mCpuBuffer = nullptr;
|
||||
MetalContext& mContext;
|
||||
@@ -171,7 +247,11 @@ public:
|
||||
mBufferOptions(options),
|
||||
mSlotSizeBytes(computeSlotSize(layout)),
|
||||
mSlotCount(slotCount) {
|
||||
mBuffer = [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions];
|
||||
{
|
||||
ScopedAllocationTimer timer("ring");
|
||||
mBuffer = [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions];
|
||||
}
|
||||
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::RING);
|
||||
assert_invariant(mBuffer);
|
||||
}
|
||||
|
||||
@@ -189,9 +269,13 @@ public:
|
||||
// If we already have an aux buffer, it will get freed here, unless it has been retained
|
||||
// by a MTLCommandBuffer. In that case, it will be freed when the command buffer
|
||||
// finishes executing.
|
||||
mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions];
|
||||
{
|
||||
ScopedAllocationTimer timer("ring");
|
||||
mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions];
|
||||
}
|
||||
MetalBufferTracking::track(mAuxBuffer, MetalBufferTracking::Type::RING);
|
||||
assert_invariant(mAuxBuffer);
|
||||
return {mAuxBuffer.get(), 0};
|
||||
return { mAuxBuffer, 0 };
|
||||
}
|
||||
mCurrentSlot = (mCurrentSlot + 1) % mSlotCount;
|
||||
mOccupiedSlots->fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -220,9 +304,9 @@ public:
|
||||
*/
|
||||
std::pair<id<MTLBuffer>, NSUInteger> getCurrentAllocation() const {
|
||||
if (UTILS_UNLIKELY(mAuxBuffer)) {
|
||||
return { mAuxBuffer.get(), 0 };
|
||||
return { mAuxBuffer, 0 };
|
||||
}
|
||||
return { mBuffer.get(), mCurrentSlot * mSlotSizeBytes };
|
||||
return { mBuffer, mCurrentSlot * mSlotSizeBytes };
|
||||
}
|
||||
|
||||
bool canAccomodateLayout(MTLSizeAndAlign layout) const {
|
||||
@@ -231,8 +315,8 @@ public:
|
||||
|
||||
private:
|
||||
id<MTLDevice> mDevice;
|
||||
TrackedMetalBuffer mBuffer;
|
||||
TrackedMetalBuffer mAuxBuffer;
|
||||
id<MTLBuffer> mBuffer;
|
||||
id<MTLBuffer> mAuxBuffer;
|
||||
|
||||
MTLResourceOptions mBufferOptions;
|
||||
|
||||
|
||||
@@ -22,7 +22,13 @@
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
std::atomic<uint64_t> TrackedMetalBuffer::aliveBuffers = 0;
|
||||
MetalPlatform* ScopedAllocationTimer::platform = nullptr;
|
||||
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
std::array<NSHashTable<id<MTLBuffer>>*, MetalBufferTracking::TypeCount>
|
||||
MetalBufferTracking::aliveBuffers;
|
||||
MetalPlatform* MetalBufferTracking::platform = nullptr;
|
||||
#endif
|
||||
|
||||
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
|
||||
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
|
||||
@@ -37,7 +43,11 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
||||
}
|
||||
|
||||
// Otherwise, we allocate a private GPU buffer.
|
||||
mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate];
|
||||
{
|
||||
ScopedAllocationTimer timer("generic");
|
||||
mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate];
|
||||
}
|
||||
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::GENERIC);
|
||||
ASSERT_POSTCONDITION(mBuffer, "Could not allocate Metal buffer of size %zu.", size);
|
||||
}
|
||||
|
||||
@@ -64,7 +74,7 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
|
||||
// Acquire a staging buffer to hold the contents of this update.
|
||||
MetalBufferPool* bufferPool = mContext.bufferPool;
|
||||
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
|
||||
memcpy(staging->buffer.get().contents, src, size);
|
||||
memcpy(staging->buffer.contents, src, size);
|
||||
|
||||
// The blit below requires that byteOffset be a multiple of 4.
|
||||
ASSERT_PRECONDITION(!(byteOffset & 0x3u), "byteOffset must be a multiple of 4");
|
||||
@@ -73,9 +83,9 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @"Buffer upload blit";
|
||||
[blitEncoder copyFromBuffer:staging->buffer.get()
|
||||
[blitEncoder copyFromBuffer:staging->buffer
|
||||
sourceOffset:0
|
||||
toBuffer:mBuffer.get()
|
||||
toBuffer:mBuffer
|
||||
destinationOffset:byteOffset
|
||||
size:size];
|
||||
[blitEncoder endEncoding];
|
||||
@@ -96,7 +106,7 @@ id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) n
|
||||
return nil;
|
||||
}
|
||||
assert_invariant(mBuffer);
|
||||
return mBuffer.get();
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncoder> encoder,
|
||||
|
||||
@@ -32,7 +32,7 @@ struct MetalContext;
|
||||
|
||||
// Immutable POD representing a shared CPU-GPU buffer.
|
||||
struct MetalBufferPoolEntry {
|
||||
TrackedMetalBuffer buffer;
|
||||
id<MTLBuffer> buffer;
|
||||
size_t capacity;
|
||||
mutable uint64_t lastAccessed;
|
||||
mutable uint32_t referenceCount;
|
||||
|
||||
@@ -42,8 +42,13 @@ MetalBufferPoolEntry const* MetalBufferPool::acquireBuffer(size_t numBytes) {
|
||||
}
|
||||
|
||||
// We were not able to find a sufficiently large stage, so create a new one.
|
||||
id<MTLBuffer> buffer = [mContext.device newBufferWithLength:numBytes
|
||||
options:MTLResourceStorageModeShared];
|
||||
id<MTLBuffer> buffer = nil;
|
||||
{
|
||||
ScopedAllocationTimer timer("staging");
|
||||
buffer = [mContext.device newBufferWithLength:numBytes
|
||||
options:MTLResourceStorageModeShared];
|
||||
}
|
||||
MetalBufferTracking::track(buffer, MetalBufferTracking::Type::STAGING);
|
||||
ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes);
|
||||
MetalBufferPoolEntry* stage = new MetalBufferPoolEntry {
|
||||
.buffer = buffer,
|
||||
|
||||
@@ -105,6 +105,10 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
driverConfig.disableHandleUseAfterFreeCheck) {
|
||||
mContext->driver = this;
|
||||
|
||||
ScopedAllocationTimer::setPlatform(platform);
|
||||
MetalBufferTracking::initialize();
|
||||
MetalBufferTracking::setPlatform(platform);
|
||||
|
||||
mContext->device = mPlatform.createDevice();
|
||||
assert_invariant(mContext->device);
|
||||
|
||||
@@ -198,6 +202,8 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
}
|
||||
|
||||
MetalDriver::~MetalDriver() noexcept {
|
||||
MetalBufferTracking::setPlatform(nullptr);
|
||||
ScopedAllocationTimer::setPlatform(nullptr);
|
||||
mContext->device = nil;
|
||||
mContext->emptyTexture = nil;
|
||||
CFRelease(mContext->textureCache);
|
||||
@@ -212,25 +218,35 @@ void MetalDriver::tick(int) {
|
||||
executeTickOps();
|
||||
}
|
||||
|
||||
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
#if defined(FILAMENT_METAL_PROFILING)
|
||||
os_signpost_interval_begin(mContext->log, mContext->signpostId, "Frame encoding", "%{public}d", frameId);
|
||||
#endif
|
||||
if (mPlatform.hasDebugUpdateStatFunc()) {
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers", TrackedMetalBuffer::getAliveBuffers());
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
const uint64_t generic = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::GENERIC);
|
||||
const uint64_t ring = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::RING);
|
||||
const uint64_t staging = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::STAGING);
|
||||
const uint64_t total = generic + ring + staging;
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers", total);
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.generic", generic);
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.ring", ring);
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging", staging);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
void MetalDriver::setFrameScheduledCallback(
|
||||
Handle<HwSwapChain> sch, CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
swapChain->setFrameScheduledCallback(callback, user);
|
||||
swapChain->setFrameScheduledCallback(handler, std::move(callback));
|
||||
}
|
||||
|
||||
void MetalDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
void MetalDriver::setFrameCompletedCallback(
|
||||
Handle<HwSwapChain> sch, CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
swapChain->setFrameCompletedCallback(handler, callback, user);
|
||||
swapChain->setFrameCompletedCallback(handler, std::move(callback));
|
||||
}
|
||||
|
||||
void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
|
||||
@@ -805,6 +821,10 @@ bool MetalDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthStencilBlitSupported(TextureFormat format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
@@ -1236,6 +1256,9 @@ void MetalDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
mContext->samplerBindings[index] = sb;
|
||||
}
|
||||
|
||||
void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {}
|
||||
|
||||
void MetalDriver::insertEventMarker(const char* string, uint32_t len) {
|
||||
|
||||
}
|
||||
@@ -1614,7 +1637,7 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"bindPipeline() without a valid command encoder.");
|
||||
|
||||
@@ -1634,7 +1657,7 @@ void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_PRECONDITION(bool(functions), "Attempting to bind an invalid Metal program.");
|
||||
functions.validate();
|
||||
|
||||
auto [fragment, vertex] = functions.getRasterFunctions();
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
@@ -71,9 +73,9 @@ public:
|
||||
|
||||
void releaseDrawable();
|
||||
|
||||
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
|
||||
void setFrameCompletedCallback(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user);
|
||||
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
|
||||
void setFrameCompletedCallback(
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
|
||||
|
||||
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
|
||||
// FrameScheduledCallback.
|
||||
@@ -110,19 +112,19 @@ private:
|
||||
MetalExternalImage externalImage;
|
||||
SwapChainType type;
|
||||
|
||||
// These two fields store a callback and user data to notify the client that a frame is ready
|
||||
// for presentation.
|
||||
// If frameScheduledCallback is nullptr, then the Metal backend automatically calls
|
||||
// presentDrawable when the frame is committed.
|
||||
// Otherwise, the Metal backend will not automatically present the frame. Instead, clients bear
|
||||
// the responsibility of presenting the frame by calling the PresentCallable object.
|
||||
FrameScheduledCallback frameScheduledCallback = nullptr;
|
||||
void* frameScheduledUserData = nullptr;
|
||||
// These fields store a callback to notify the client that a frame is ready for presentation. If
|
||||
// !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the
|
||||
// frame is committed. Otherwise, the Metal backend will not automatically present the frame.
|
||||
// Instead, clients bear the responsibility of presenting the frame by calling the
|
||||
// PresentCallable object.
|
||||
struct {
|
||||
CallbackHandler* handler = nullptr;
|
||||
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
|
||||
} frameScheduled;
|
||||
|
||||
struct {
|
||||
CallbackHandler* handler = nullptr;
|
||||
CallbackHandler::Callback callback = {};
|
||||
void* user = nullptr;
|
||||
std::shared_ptr<utils::Invocable<void(void)>> callback = nullptr;
|
||||
} frameCompleted;
|
||||
};
|
||||
|
||||
|
||||
@@ -221,16 +221,16 @@ void MetalSwapChain::ensureDepthStencilTexture() {
|
||||
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) {
|
||||
frameScheduledCallback = callback;
|
||||
frameScheduledUserData = user;
|
||||
void MetalSwapChain::setFrameScheduledCallback(
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
frameScheduled.handler = handler;
|
||||
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user) {
|
||||
void MetalSwapChain::setFrameCompletedCallback(
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
frameCompleted.handler = handler;
|
||||
frameCompleted.callback = callback;
|
||||
frameCompleted.user = user;
|
||||
frameCompleted.callback = std::make_shared<utils::Invocable<void(void)>>(std::move(callback));
|
||||
}
|
||||
|
||||
void MetalSwapChain::present() {
|
||||
@@ -238,7 +238,7 @@ void MetalSwapChain::present() {
|
||||
scheduleFrameCompletedCallback();
|
||||
}
|
||||
if (drawable) {
|
||||
if (frameScheduledCallback) {
|
||||
if (frameScheduled.callback) {
|
||||
scheduleFrameScheduledCallback();
|
||||
} else {
|
||||
[getPendingCommandBuffer(&context) presentDrawable:drawable];
|
||||
@@ -296,21 +296,38 @@ void presentDrawable(bool presentFrame, void* user) {
|
||||
}
|
||||
|
||||
void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
if (!frameScheduledCallback) {
|
||||
if (!frameScheduled.callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert_invariant(drawable);
|
||||
|
||||
// Destroy this by calling maybePresentAndDestroyAsync() later.
|
||||
auto* presentData = PresentDrawableData::create(drawable, context.driver);
|
||||
struct Callback {
|
||||
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
|
||||
MetalDriver* driver)
|
||||
: f(callback), data(PresentDrawableData::create(drawable, driver)) {}
|
||||
std::shared_ptr<FrameScheduledCallback> f;
|
||||
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
|
||||
std::unique_ptr<PresentDrawableData> data;
|
||||
static void func(void* user) {
|
||||
auto* const c = reinterpret_cast<Callback*>(user);
|
||||
PresentDrawableData* presentDrawableData = c->data.release();
|
||||
PresentCallable presentCallable(presentDrawable, presentDrawableData);
|
||||
c->f->operator()(presentCallable);
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
|
||||
FrameScheduledCallback userCallback = frameScheduledCallback;
|
||||
void* userData = frameScheduledUserData;
|
||||
// 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.
|
||||
__block auto callback =
|
||||
std::make_unique<Callback>(frameScheduled.callback, drawable, context.driver);
|
||||
|
||||
backend::CallbackHandler* handler = frameScheduled.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
PresentCallable callable(presentDrawable, static_cast<void*>(presentData));
|
||||
userCallback(callable, userData);
|
||||
Callback* user = callback.release();
|
||||
driver->scheduleCallback(handler, user, &Callback::func);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -319,13 +336,25 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
|
||||
return;
|
||||
}
|
||||
|
||||
CallbackHandler* handler = frameCompleted.handler;
|
||||
void* user = frameCompleted.user;
|
||||
CallbackHandler::Callback callback = frameCompleted.callback;
|
||||
struct Callback {
|
||||
Callback(std::shared_ptr<utils::Invocable<void(void)>> callback) : f(callback) {}
|
||||
std::shared_ptr<utils::Invocable<void(void)>> f;
|
||||
static void func(void* user) {
|
||||
auto* const c = reinterpret_cast<Callback*>(user);
|
||||
c->f->operator()();
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
|
||||
// This callback pointer will be captured by the block. Even if the completed handler is never
|
||||
// called, the unique_ptr will still ensure we don't leak memory.
|
||||
__block auto callback = std::make_unique<Callback>(frameCompleted.callback);
|
||||
|
||||
CallbackHandler* handler = frameCompleted.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
driver->scheduleCallback(handler, user, callback);
|
||||
Callback* user = callback.release();
|
||||
driver->scheduleCallback(handler, user, &Callback::func);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -771,13 +800,13 @@ void MetalTexture::loadWithCopyBuffer(uint32_t level, uint32_t slice, MTLRegion
|
||||
PixelBufferDescriptor const& data, const PixelBufferShape& shape) {
|
||||
const size_t stagingBufferSize = shape.totalBytes;
|
||||
auto entry = context.bufferPool->acquireBuffer(stagingBufferSize);
|
||||
memcpy(entry->buffer.get().contents,
|
||||
memcpy(entry->buffer.contents,
|
||||
static_cast<uint8_t*>(data.buffer) + shape.sourceOffset,
|
||||
stagingBufferSize);
|
||||
id<MTLCommandBuffer> blitCommandBuffer = getPendingCommandBuffer(&context);
|
||||
id<MTLBlitCommandEncoder> blitCommandEncoder = [blitCommandBuffer blitCommandEncoder];
|
||||
blitCommandEncoder.label = @"Texture upload buffer blit";
|
||||
[blitCommandEncoder copyFromBuffer:entry->buffer.get()
|
||||
[blitCommandEncoder copyFromBuffer:entry->buffer
|
||||
sourceOffset:0
|
||||
sourceBytesPerRow:shape.bytesPerRow
|
||||
sourceBytesPerImage:shape.bytesPerSlice
|
||||
|
||||
@@ -106,26 +106,41 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_invariant(source[source.size() - 1] == '\0');
|
||||
|
||||
// the shader string is null terminated and the length includes the null character
|
||||
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
|
||||
length:source.size() - 1
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
// By default, Metal uses the most recent language version.
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
|
||||
// Disable Fast Math optimizations.
|
||||
// This ensures that operations adhere to IEEE standards for floating-point arithmetic,
|
||||
// which is crucial for half precision floats in scenarios where fast math optimizations
|
||||
// lead to inaccuracies, such as in handling special values like NaN or Infinity.
|
||||
options.fastMathEnabled = NO;
|
||||
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
id<MTLLibrary> library = nil;
|
||||
switch (program.getShaderLanguage()) {
|
||||
case ShaderLanguage::MSL: {
|
||||
// By default, Metal uses the most recent language version.
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
|
||||
// Disable Fast Math optimizations.
|
||||
// This ensures that operations adhere to IEEE standards for floating-point
|
||||
// arithmetic, which is crucial for half precision floats in scenarios where fast
|
||||
// math optimizations lead to inaccuracies, such as in handling special values like
|
||||
// NaN or Infinity.
|
||||
options.fastMathEnabled = NO;
|
||||
|
||||
assert_invariant(source[source.size() - 1] == '\0');
|
||||
// the shader string is null terminated and the length includes the null character
|
||||
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
|
||||
length:source.size() - 1
|
||||
encoding:NSUTF8StringEncoding];
|
||||
library = [device newLibraryWithSource:objcSource options:options error:&error];
|
||||
break;
|
||||
}
|
||||
case ShaderLanguage::METAL_LIBRARY: {
|
||||
dispatch_data_t data = dispatch_data_create(source.data(), source.size(),
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
|
||||
library = [device newLibraryWithData:data error:&error];
|
||||
break;
|
||||
}
|
||||
case ShaderLanguage::ESSL1:
|
||||
case ShaderLanguage::ESSL3:
|
||||
case ShaderLanguage::SPIRV:
|
||||
break;
|
||||
}
|
||||
|
||||
if (library == nil) {
|
||||
NSString* errorMessage = @"unknown error";
|
||||
if (error) {
|
||||
@@ -154,6 +169,23 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"main0"
|
||||
constantValues:constants
|
||||
error:&error];
|
||||
if (function == nil) {
|
||||
// If the library loads but functions within it fail to load, it usually means the
|
||||
// GPU backend crashed. (This can happen if it's a Metallib shader that was compiled
|
||||
// with a minimum iOS version that's newer than this device.)
|
||||
NSString* errorMessage = @"unknown error";
|
||||
if (error) {
|
||||
auto description =
|
||||
[error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
utils::slog.w << description << utils::io::endl;
|
||||
errorMessage = error.localizedDescription;
|
||||
}
|
||||
PANIC_LOG("Failed to load main0 in Metal program.");
|
||||
NSString* programName =
|
||||
[NSString stringWithFormat:@"%s::main0", program.getName().c_str_safe()];
|
||||
return MetalFunctionBundle::error(errorMessage, programName);
|
||||
}
|
||||
|
||||
if (!program.getName().empty()) {
|
||||
function.label = @(program.getName().c_str());
|
||||
}
|
||||
|
||||
@@ -49,16 +49,17 @@ void NoopDriver::terminate() {
|
||||
void NoopDriver::tick(int) {
|
||||
}
|
||||
|
||||
void NoopDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
void NoopDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
}
|
||||
|
||||
void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
|
||||
}
|
||||
|
||||
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
|
||||
}
|
||||
|
||||
@@ -193,6 +194,10 @@ bool NoopDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isDepthStencilBlitSupported(TextureFormat format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isProtectedTexturesSupported() {
|
||||
return true;
|
||||
}
|
||||
@@ -307,6 +312,10 @@ void NoopDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
|
||||
void NoopDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
}
|
||||
|
||||
void NoopDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
}
|
||||
|
||||
void NoopDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
}
|
||||
|
||||
@@ -350,7 +359,7 @@ void NoopDriver::blit(
|
||||
math::uint2 size) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindPipeline(PipelineState pipelineState) {
|
||||
void NoopDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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_GLBUFFEROBJECT_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct GLBufferObject : public HwBufferObject {
|
||||
using HwBufferObject::HwBufferObject;
|
||||
GLBufferObject(uint32_t size,
|
||||
BufferObjectBinding bindingType, BufferUsage usage) noexcept
|
||||
: HwBufferObject(size), usage(usage), bindingType(bindingType) {
|
||||
}
|
||||
|
||||
struct {
|
||||
GLuint id;
|
||||
union {
|
||||
GLenum binding;
|
||||
void* buffer;
|
||||
};
|
||||
} gl;
|
||||
BufferUsage usage;
|
||||
BufferObjectBinding bindingType;
|
||||
uint16_t age = 0;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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_GLTEXTURE_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct GLTexture : public HwTexture {
|
||||
using HwTexture::HwTexture;
|
||||
struct GL {
|
||||
GL() noexcept : imported(false), sidecarSamples(1), reserved1(0) {}
|
||||
GLuint id = 0; // texture or renderbuffer id
|
||||
GLenum target = 0;
|
||||
GLenum internalFormat = 0;
|
||||
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
|
||||
|
||||
// texture parameters go here too
|
||||
GLfloat anisotropy = 1.0;
|
||||
int8_t baseLevel = 127;
|
||||
int8_t maxLevel = -1;
|
||||
uint8_t reserved0 = 0;
|
||||
bool imported : 1;
|
||||
uint8_t sidecarSamples : 4;
|
||||
uint8_t reserved1 : 3;
|
||||
} gl;
|
||||
|
||||
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
|
||||
};
|
||||
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H
|
||||
@@ -63,8 +63,10 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
|
||||
: mPlatform(platform) {
|
||||
OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
||||
Platform::DriverConfig const& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mSamplerMap(32) {
|
||||
|
||||
state.vao.p = &mDefaultVAO;
|
||||
|
||||
@@ -84,6 +86,13 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
|
||||
|
||||
queryOpenGLVersion(&state.major, &state.minor);
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GLES)
|
||||
if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) {
|
||||
state.major = 2;
|
||||
state.minor = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
OpenGLContext::initExtensions(&ext, state.major, state.minor);
|
||||
|
||||
OpenGLContext::initProcs(&procs, ext, state.major, state.minor);
|
||||
@@ -252,9 +261,23 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
|
||||
}
|
||||
|
||||
OpenGLContext::~OpenGLContext() noexcept {
|
||||
// note: this is called from the main thread. Can't do any GL calls.
|
||||
delete mTimerQueryFactory;
|
||||
}
|
||||
|
||||
void OpenGLContext::terminate() noexcept {
|
||||
// note: this is called from the backend thread
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!isES2()) {
|
||||
for (auto& item: mSamplerMap) {
|
||||
unbindSampler(item.second);
|
||||
glDeleteSamplers(1, &item.second);
|
||||
}
|
||||
mSamplerMap.clear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::destroyWithContext(
|
||||
size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept {
|
||||
if (index == 0) {
|
||||
@@ -516,8 +539,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
bugs->delay_fbo_destruction = true;
|
||||
// PowerVR seems to have no problem with this (which is good for us)
|
||||
bugs->allow_read_only_ancillary_feedback_loop = true;
|
||||
// PowerVR has a shader compiler thread pinned on the last core
|
||||
bugs->disable_thread_affinity = true;
|
||||
} else if (strstr(renderer, "Apple")) {
|
||||
// Apple GPU
|
||||
} else if (strstr(renderer, "Tegra") ||
|
||||
@@ -868,19 +889,32 @@ default_case:
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindTexture(GLenum target, GLuint texture_id) noexcept {
|
||||
void OpenGLContext::unbindTexture(
|
||||
UTILS_UNUSED_IN_RELEASE GLenum target, GLuint texture_id) noexcept {
|
||||
// unbind this texture from all the units it might be bound to
|
||||
// no need unbind the texture from FBOs because we're not tracking that state (and there is
|
||||
// no need to).
|
||||
const size_t index = getIndexForTextureTarget(target);
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].targets[index].texture_id == texture_id) {
|
||||
bindTexture(unit, target, (GLuint)0, index);
|
||||
// Never attempt to unbind texture 0. This could happen with external textures w/ streaming if
|
||||
// never populated.
|
||||
if (texture_id) {
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].id == texture_id) {
|
||||
// if this texture is bound, it should be at the same target
|
||||
assert_invariant(state.textures.units[unit].target == target);
|
||||
unbindTextureUnit(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindTextureUnit(GLuint unit) noexcept {
|
||||
update_state(state.textures.units[unit].id, 0u, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(state.textures.units[unit].target, 0u);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
|
||||
// unbind this sampler from all the units it might be bound to
|
||||
UTILS_NOUNROLL // clang generates >800B of code!!!
|
||||
@@ -934,6 +968,37 @@ void OpenGLContext::deleteVertexArray(GLuint vao) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint OpenGLContext::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
|
||||
|
||||
using namespace GLUtils;
|
||||
|
||||
GLuint s;
|
||||
glGenSamplers(1, &s);
|
||||
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
|
||||
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
|
||||
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
if (ext.EXT_texture_filter_anisotropic &&
|
||||
!bugs.texture_filter_anisotropic_broken_on_sampler) {
|
||||
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
|
||||
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
std::min(gets.max_anisotropy, anisotropy));
|
||||
}
|
||||
#endif
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
mSamplerMap[params] = s;
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void OpenGLContext::resetState() noexcept {
|
||||
// Force GL state to match the Filament state
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
@@ -89,9 +91,13 @@ public:
|
||||
|
||||
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
|
||||
|
||||
explicit OpenGLContext(OpenGLPlatform& platform) noexcept;
|
||||
explicit OpenGLContext(OpenGLPlatform& platform,
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
|
||||
~OpenGLContext() noexcept final;
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
// TimerQueryInterface ------------------------------------------------------------------------
|
||||
|
||||
// note: OpenGLContext being final ensures (clang) these are not called through the vtable
|
||||
@@ -132,7 +138,6 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr static inline size_t getIndexForTextureTarget(GLuint target) noexcept;
|
||||
constexpr inline size_t getIndexForCap(GLenum cap) noexcept;
|
||||
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
|
||||
|
||||
@@ -144,10 +149,10 @@ public:
|
||||
|
||||
void pixelStore(GLenum, GLint) noexcept;
|
||||
inline void activeTexture(GLuint unit) noexcept;
|
||||
inline void bindTexture(GLuint unit, GLuint target, GLuint texId, size_t targetIndex) noexcept;
|
||||
inline void bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept;
|
||||
|
||||
void unbindTexture(GLenum target, GLuint id) noexcept;
|
||||
void unbindTextureUnit(GLuint unit) noexcept;
|
||||
inline void bindVertexArray(RenderPrimitive const* p) noexcept;
|
||||
inline void bindSampler(GLuint unit, GLuint sampler) noexcept;
|
||||
void unbindSampler(GLuint sampler) noexcept;
|
||||
@@ -306,10 +311,6 @@ public:
|
||||
// a glFinish. So we must delay the destruction until we know the GPU is finished.
|
||||
bool delay_fbo_destruction;
|
||||
|
||||
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
|
||||
// performance more if we end-up pinned on the same one.
|
||||
bool disable_thread_affinity;
|
||||
|
||||
// Force feature level 0. Typically used for low end ES3 devices with significant driver
|
||||
// bugs or performance issues.
|
||||
bool force_feature_level0;
|
||||
@@ -321,8 +322,14 @@ public:
|
||||
|
||||
// function to handle state changes we don't control
|
||||
void updateTexImage(GLenum target, GLuint id) noexcept {
|
||||
const size_t index = getIndexForTextureTarget(target);
|
||||
state.textures.units[state.textures.active].targets[index].texture_id = id;
|
||||
assert_invariant(target == GL_TEXTURE_EXTERNAL_OES);
|
||||
// if another target is bound to this texture unit, unbind that texture
|
||||
if (UTILS_UNLIKELY(state.textures.units[state.textures.active].target != target)) {
|
||||
glBindTexture(state.textures.units[state.textures.active].target, 0);
|
||||
state.textures.units[state.textures.active].target = GL_TEXTURE_EXTERNAL_OES;
|
||||
}
|
||||
// the texture is already bound to `target`, we just update our internal state
|
||||
state.textures.units[state.textures.active].id = id;
|
||||
}
|
||||
void resetProgram() noexcept { state.program.use = 0; }
|
||||
|
||||
@@ -425,9 +432,8 @@ public:
|
||||
GLuint active = 0; // zero-based
|
||||
struct {
|
||||
GLuint sampler = 0;
|
||||
struct {
|
||||
GLuint texture_id = 0;
|
||||
} targets[7]; // this must match getIndexForTextureTarget()
|
||||
GLuint target = 0;
|
||||
GLuint id = 0;
|
||||
} units[MAX_TEXTURE_UNIT_COUNT];
|
||||
} textures;
|
||||
|
||||
@@ -467,6 +473,29 @@ public:
|
||||
|
||||
void unbindEverything() noexcept;
|
||||
void synchronizeStateAndCache(size_t index) noexcept;
|
||||
void setEs2UniformBinding(size_t index, GLuint id, void const* data, uint16_t age) noexcept {
|
||||
mUniformBindings[index] = { id, data, age };
|
||||
}
|
||||
auto getEs2UniformBinding(size_t index) const noexcept {
|
||||
return mUniformBindings[index];
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
|
||||
|
||||
inline GLuint getSampler(SamplerParams sp) const noexcept {
|
||||
assert_invariant(!sp.padding0);
|
||||
assert_invariant(!sp.padding1);
|
||||
assert_invariant(!sp.padding2);
|
||||
auto& samplerMap = mSamplerMap;
|
||||
auto pos = samplerMap.find(sp);
|
||||
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
|
||||
return getSamplerSlow(sp);
|
||||
}
|
||||
return pos->second;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
private:
|
||||
OpenGLPlatform& mPlatform;
|
||||
@@ -476,6 +505,11 @@ private:
|
||||
std::vector<std::function<void(OpenGLContext&)>> mDestroyWithNormalContext;
|
||||
RenderPrimitive mDefaultVAO;
|
||||
std::optional<GLuint> mDefaultFbo[2];
|
||||
std::array<
|
||||
std::tuple<GLuint, void const*, uint16_t>,
|
||||
CONFIG_UNIFORM_BINDING_COUNT> mUniformBindings = {};
|
||||
mutable tsl::robin_map<SamplerParams, GLuint,
|
||||
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
|
||||
|
||||
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
|
||||
|
||||
@@ -522,9 +556,6 @@ private:
|
||||
{ bugs.delay_fbo_destruction,
|
||||
"delay_fbo_destruction",
|
||||
""},
|
||||
{ bugs.disable_thread_affinity,
|
||||
"disable_thread_affinity",
|
||||
""},
|
||||
{ bugs.force_feature_level0,
|
||||
"force_feature_level0",
|
||||
""},
|
||||
@@ -572,31 +603,10 @@ private:
|
||||
}
|
||||
|
||||
void setDefaultState() noexcept;
|
||||
|
||||
static constexpr const size_t TEXTURE_TARGET_COUNT =
|
||||
sizeof(state.textures.units[0].targets) / sizeof(state.textures.units[0].targets[0]);
|
||||
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
constexpr size_t OpenGLContext::getIndexForTextureTarget(GLuint target) noexcept {
|
||||
// this must match state.textures[].targets[]
|
||||
switch (target) {
|
||||
case GL_TEXTURE_2D: return 0;
|
||||
case GL_TEXTURE_2D_ARRAY: return 1;
|
||||
case GL_TEXTURE_CUBE_MAP: return 2;
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
case GL_TEXTURE_2D_MULTISAMPLE: return 3;
|
||||
#endif
|
||||
case GL_TEXTURE_EXTERNAL_OES: return 4;
|
||||
case GL_TEXTURE_3D: return 5;
|
||||
case GL_TEXTURE_CUBE_MAP_ARRAY: return 6;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
|
||||
size_t index = 0;
|
||||
switch (cap) {
|
||||
@@ -744,19 +754,17 @@ void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId, size_t targetIndex) noexcept {
|
||||
assert_invariant(targetIndex == getIndexForTextureTarget(target));
|
||||
assert_invariant(targetIndex < TEXTURE_TARGET_COUNT);
|
||||
update_state(state.textures.units[unit].targets[targetIndex].texture_id, texId, [&]() {
|
||||
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept {
|
||||
update_state(state.textures.units[unit].target, target, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(state.textures.units[unit].target, 0);
|
||||
});
|
||||
update_state(state.textures.units[unit].id, texId, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(target, texId);
|
||||
}, target == GL_TEXTURE_EXTERNAL_OES);
|
||||
}
|
||||
|
||||
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept {
|
||||
bindTexture(unit, target, texId, getIndexForTextureTarget(target));
|
||||
}
|
||||
|
||||
void OpenGLContext::useProgram(GLuint program) noexcept {
|
||||
update_state(state.program.use, program, [&]() {
|
||||
glUseProgram(program);
|
||||
|
||||
@@ -173,6 +173,10 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
PANIC_LOG("OpenGL ES 2.0 minimum needed (current %d.%d)", major, minor);
|
||||
goto cleanup;
|
||||
}
|
||||
if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) {
|
||||
major = 2;
|
||||
minor = 0;
|
||||
}
|
||||
#else
|
||||
// we require GL 4.1 headers and minimum version
|
||||
if (UTILS_UNLIKELY(!((major == 4 && minor >= 1) || major > 4))) {
|
||||
@@ -203,13 +207,13 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
|
||||
|
||||
OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(*platform),
|
||||
mContext(mPlatform),
|
||||
mContext(mPlatform, driverConfig),
|
||||
mShaderCompilerService(*this),
|
||||
mHandleAllocator("Handles",
|
||||
driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck),
|
||||
mSamplerMap(32),
|
||||
mDriverConfig(driverConfig) {
|
||||
mDriverConfig(driverConfig),
|
||||
mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) {
|
||||
|
||||
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
|
||||
|
||||
@@ -233,10 +237,19 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
||||
}
|
||||
|
||||
OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
|
||||
// this is called from the main thread. Can't call GL.
|
||||
}
|
||||
|
||||
Dispatcher OpenGLDriver::getDispatcher() const noexcept {
|
||||
return ConcreteDispatcher<OpenGLDriver>::make();
|
||||
auto dispatcher = ConcreteDispatcher<OpenGLDriver>::make();
|
||||
if (mContext.isES2()) {
|
||||
dispatcher.draw2_ = +[](Driver& driver, CommandBase* base, intptr_t* next){
|
||||
using Cmd = COMMAND_TYPE(draw2);
|
||||
OpenGLDriver& concreteDriver = static_cast<OpenGLDriver&>(driver);
|
||||
Cmd::execute(&OpenGLDriver::draw2GLES2, concreteDriver, base, next);
|
||||
};
|
||||
}
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -263,16 +276,13 @@ void OpenGLDriver::terminate() {
|
||||
|
||||
// because we called glFinish(), all callbacks should have been executed
|
||||
assert_invariant(mGpuCommandCompleteOps.empty());
|
||||
|
||||
if (!getContext().isES2()) {
|
||||
for (auto& item: mSamplerMap) {
|
||||
mContext.unbindSampler(item.second);
|
||||
glDeleteSamplers(1, &item.second);
|
||||
}
|
||||
mSamplerMap.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
delete mCurrentPushConstants;
|
||||
mCurrentPushConstants = nullptr;
|
||||
|
||||
mContext.terminate();
|
||||
|
||||
mPlatform.terminate();
|
||||
}
|
||||
|
||||
@@ -291,9 +301,42 @@ void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
||||
mContext.bindSampler(unit, sampler);
|
||||
}
|
||||
|
||||
void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT);
|
||||
utils::Slice<std::pair<GLint, ConstantType>> constants;
|
||||
if (stage == ShaderStage::VERTEX) {
|
||||
constants = mCurrentPushConstants->vertexConstants;
|
||||
} else if (stage == ShaderStage::FRAGMENT) {
|
||||
constants = mCurrentPushConstants->fragmentConstants;
|
||||
}
|
||||
|
||||
assert_invariant(index < constants.size());
|
||||
auto const& [location, type] = constants[index];
|
||||
|
||||
// This push constant wasn't found in the shader. It's ok to return without error-ing here.
|
||||
if (location < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<bool>(value)) {
|
||||
assert_invariant(type == ConstantType::BOOL);
|
||||
bool const bval = std::get<bool>(value);
|
||||
glUniform1i(location, bval ? 1 : 0);
|
||||
} else if (std::holds_alternative<float>(value)) {
|
||||
assert_invariant(type == ConstantType::FLOAT);
|
||||
float const fval = std::get<float>(value);
|
||||
glUniform1f(location, fval);
|
||||
} else {
|
||||
assert_invariant(type == ConstantType::INT);
|
||||
int const ival = std::get<int>(value);
|
||||
glUniform1i(location, ival);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
assert_invariant(t != nullptr);
|
||||
mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.targetIndex);
|
||||
mContext.bindTexture(unit, t->gl.target, t->gl.id);
|
||||
}
|
||||
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
@@ -307,7 +350,7 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
|
||||
auto [id, buffer, age] = mUniformBindings[i];
|
||||
auto [id, buffer, age] = mContext.getEs2UniformBinding(i);
|
||||
if (buffer) {
|
||||
p->updateUniforms(i, id, buffer, age);
|
||||
}
|
||||
@@ -724,10 +767,19 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
GLenum internalFormat = getInternalFormat(format);
|
||||
assert_invariant(internalFormat);
|
||||
|
||||
if (UTILS_UNLIKELY(usage & TextureUsage::PROTECTED)) {
|
||||
if (any(usage & TextureUsage::PROTECTED)) {
|
||||
// renderbuffers don't have a protected mode, so we need to use a texture
|
||||
// because protected textures are only supported on GLES 3.2, MSAA will be available.
|
||||
usage |= TextureUsage::SAMPLEABLE;
|
||||
} else if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
// if we have the uploadable flag, we also need to use a texture
|
||||
usage |= TextureUsage::SAMPLEABLE;
|
||||
} else if (target != SamplerType::SAMPLER_2D) {
|
||||
// renderbuffers can only be 2D
|
||||
usage |= TextureUsage::SAMPLEABLE;
|
||||
} else if (levels > 1) {
|
||||
// renderbuffers can't have mip levels
|
||||
usage |= TextureUsage::SAMPLEABLE;
|
||||
}
|
||||
|
||||
auto& gl = mContext;
|
||||
@@ -746,7 +798,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
if (t->externalTexture) {
|
||||
t->gl.target = t->externalTexture->target;
|
||||
t->gl.id = t->externalTexture->id;
|
||||
t->gl.targetIndex = (uint8_t)OpenGLContext::getIndexForTextureTarget(t->gl.target);
|
||||
// internalFormat actually depends on the external image, but it doesn't matter
|
||||
// because it's not used anywhere for anything important.
|
||||
t->gl.internalFormat = internalFormat;
|
||||
@@ -758,30 +809,23 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
|
||||
t->gl.internalFormat = internalFormat;
|
||||
|
||||
// We DO NOT update targetIndex at function exit to take advantage of the fact that
|
||||
// getIndexForTextureTarget() is constexpr -- so all of this disappears at compile time.
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
// we can't be here -- doesn't matter what we do
|
||||
case SamplerType::SAMPLER_2D:
|
||||
t->gl.target = GL_TEXTURE_2D;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D);
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
t->gl.target = GL_TEXTURE_3D;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_3D);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
t->gl.target = GL_TEXTURE_2D_ARRAY;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY);
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
t->gl.target = GL_TEXTURE_CUBE_MAP;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP);
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
t->gl.target = GL_TEXTURE_CUBE_MAP_ARRAY;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -792,8 +836,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
if (gl.features.multisample_texture) {
|
||||
// multi-sample texture on GL 3.2 / GLES 3.1 and above
|
||||
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
|
||||
t->gl.targetIndex = (uint8_t)
|
||||
OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE);
|
||||
} else {
|
||||
// Turn off multi-sampling for that texture. It's just not supported.
|
||||
}
|
||||
@@ -803,13 +845,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
textureStorage(t, w, h, depth, bool(usage & TextureUsage::PROTECTED));
|
||||
}
|
||||
} else {
|
||||
assert_invariant(any(usage & (
|
||||
TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::DEPTH_ATTACHMENT |
|
||||
TextureUsage::STENCIL_ATTACHMENT)));
|
||||
assert_invariant(levels == 1);
|
||||
assert_invariant(target == SamplerType::SAMPLER_2D);
|
||||
assert_invariant(none(usage & TextureUsage::PROTECTED));
|
||||
t->gl.internalFormat = internalFormat;
|
||||
t->gl.target = GL_RENDERBUFFER;
|
||||
glGenRenderbuffers(1, &t->gl.id);
|
||||
@@ -859,32 +894,24 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||
t->gl.internalFormat = getInternalFormat(format);
|
||||
assert_invariant(t->gl.internalFormat);
|
||||
|
||||
// We DO NOT update targetIndex at function exit to take advantage of the fact that
|
||||
// getIndexForTextureTarget() is constexpr -- so all of this disappears at compile time.
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
t->gl.target = GL_TEXTURE_EXTERNAL_OES;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_EXTERNAL_OES);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D:
|
||||
t->gl.target = GL_TEXTURE_2D;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D);
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
t->gl.target = GL_TEXTURE_3D;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_3D);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
t->gl.target = GL_TEXTURE_2D_ARRAY;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY);
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
t->gl.target = GL_TEXTURE_CUBE_MAP;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP);
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
t->gl.target = GL_TEXTURE_CUBE_MAP_ARRAY;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -895,7 +922,6 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||
if (gl.features.multisample_texture) {
|
||||
// multi-sample texture on GL 3.2 / GLES 3.1 and above
|
||||
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
|
||||
t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE);
|
||||
} else {
|
||||
// Turn off multi-sampling for that texture. It's just not supported.
|
||||
}
|
||||
@@ -906,16 +932,18 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) {
|
||||
// NOTE: this is called from draw() and must be as efficient as possible.
|
||||
// NOTE: this is called often and must be as efficient as possible.
|
||||
|
||||
auto& gl = mContext;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) {
|
||||
// The VAO for the given render primitive must already be bound.
|
||||
GLint vaoBinding;
|
||||
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding);
|
||||
assert_invariant(vaoBinding == (GLint)rp->gl.vao[gl.contextIndex]);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (UTILS_LIKELY(rp->gl.vertexBufferVersion == vb->bufferObjectsVersion &&
|
||||
rp->gl.stateVersion == gl.state.age)) {
|
||||
@@ -931,7 +959,7 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer
|
||||
// if a buffer is defined it must not be invalid.
|
||||
assert_invariant(vb->gl.buffers[bi]);
|
||||
|
||||
// if w're on ES2, the user shouldn't use FLAG_INTEGER_TARGET
|
||||
// if we're on ES2, the user shouldn't use FLAG_INTEGER_TARGET
|
||||
assert_invariant(!(gl.isES2() && (attribute.flags & Attribute::FLAG_INTEGER_TARGET)));
|
||||
|
||||
gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]);
|
||||
@@ -1785,7 +1813,6 @@ void OpenGLDriver::updateStreams(DriverApi* driver) {
|
||||
// the target and id can be reset each time
|
||||
t->gl.target = t->externalTexture->target;
|
||||
t->gl.id = t->externalTexture->id;
|
||||
t->gl.targetIndex = (uint8_t)OpenGLContext::getIndexForTextureTarget(t->gl.target);
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
|
||||
}
|
||||
}
|
||||
@@ -2047,6 +2074,10 @@ bool OpenGLDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isDepthStencilBlitSupported(TextureFormat format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isProtectedTexturesSupported() {
|
||||
return getContext().ext.EXT_protected_textures;
|
||||
}
|
||||
@@ -2063,8 +2094,6 @@ bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
return mContext.bugs.disable_blit_into_texture_array;
|
||||
case Workaround::POWER_VR_SHADER_WORKAROUNDS:
|
||||
return mContext.bugs.powervr_shader_workarounds;
|
||||
case Workaround::DISABLE_THREAD_AFFINITY:
|
||||
return mContext.bugs.disable_thread_affinity;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2343,7 +2372,7 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
#endif
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_LIKELY(!es2)) {
|
||||
samplerId = getSampler(params);
|
||||
samplerId = mContext.getSampler(params);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
@@ -2472,7 +2501,9 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
|
||||
size_t const stride = p.stride ? p.stride : width;
|
||||
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
|
||||
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
|
||||
void const* const buffer = static_cast<char const*>(p.buffer) + p.left * bpp + bpr * p.top;
|
||||
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
|
||||
void const* const buffer = static_cast<char const*>(p.buffer)
|
||||
+ bpp* p.left + bpr * p.top + bpl * 0; // TODO: PBD should have a p.depth
|
||||
|
||||
switch (t->target) {
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
@@ -2671,7 +2702,6 @@ void OpenGLDriver::setExternalImage(Handle<HwTexture> th, void* image) {
|
||||
// the target and id can be reset each time
|
||||
t->gl.target = t->externalTexture->target;
|
||||
t->gl.id = t->externalTexture->id;
|
||||
t->gl.targetIndex = (uint8_t)OpenGLContext::getIndexForTextureTarget(t->gl.target);
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
|
||||
}
|
||||
}
|
||||
@@ -3066,11 +3096,10 @@ void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind
|
||||
assert_invariant(offset + size <= ub->byteCount);
|
||||
|
||||
if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
mUniformBindings[index] = {
|
||||
gl.setEs2UniformBinding(index,
|
||||
ub->gl.id,
|
||||
static_cast<uint8_t const*>(ub->gl.buffer) + offset,
|
||||
ub->age,
|
||||
};
|
||||
ub->age);
|
||||
} else {
|
||||
GLenum const target = GLUtils::getBufferBindingType(bindingType);
|
||||
|
||||
@@ -3088,7 +3117,7 @@ void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index)
|
||||
auto& gl = mContext;
|
||||
|
||||
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
mUniformBindings[index] = {};
|
||||
gl.setEs2UniformBinding(index, 0, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3105,36 +3134,6 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
|
||||
|
||||
GLuint s;
|
||||
glGenSamplers(1, &s);
|
||||
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
|
||||
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
|
||||
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_texture_filter_anisotropic &&
|
||||
!gl.bugs.texture_filter_anisotropic_broken_on_sampler) {
|
||||
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
|
||||
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
std::min(gl.gets.max_anisotropy, anisotropy));
|
||||
}
|
||||
#endif
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
mSamplerMap[params] = s;
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
@@ -3434,17 +3433,21 @@ void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
|
||||
|
||||
void OpenGLDriver::tick(int) {
|
||||
DEBUG_MARKER()
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
executeGpuCommandsCompleteOps();
|
||||
#endif
|
||||
executeEveryNowAndThenOps();
|
||||
getShaderCompilerService().tick();
|
||||
}
|
||||
|
||||
void OpenGLDriver::beginFrame(
|
||||
UTILS_UNUSED int64_t monotonic_clock_ns,
|
||||
UTILS_UNUSED int64_t refreshIntervalNs,
|
||||
UTILS_UNUSED uint32_t frameId) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
insertEventMarker("beginFrame");
|
||||
mPlatform.beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
|
||||
if (UTILS_UNLIKELY(!mTexturesWithStreamsAttached.empty())) {
|
||||
OpenGLPlatform& platform = mPlatform;
|
||||
for (GLTexture const* t : mTexturesWithStreamsAttached) {
|
||||
@@ -3453,7 +3456,7 @@ void OpenGLDriver::beginFrame(
|
||||
assert_invariant(t->hwStream->stream);
|
||||
platform.updateTexImage(t->hwStream->stream,
|
||||
&static_cast<GLStream*>(t->hwStream)->user_thread.timestamp); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||
// NOTE: We assume that updateTexImage() binds the texture on our behalf
|
||||
// NOTE: We assume that OpenGLPlatform::updateTexImage() binds the texture on our behalf
|
||||
gl.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id);
|
||||
}
|
||||
}
|
||||
@@ -3461,12 +3464,12 @@ void OpenGLDriver::beginFrame(
|
||||
}
|
||||
|
||||
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
DEBUG_MARKER()
|
||||
}
|
||||
|
||||
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
DEBUG_MARKER()
|
||||
}
|
||||
|
||||
@@ -3492,6 +3495,7 @@ void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
|
||||
#endif
|
||||
//SYSTRACE_NAME("glFinish");
|
||||
//glFinish();
|
||||
mPlatform.endFrame(frameId);
|
||||
insertEventMarker("endFrame");
|
||||
}
|
||||
|
||||
@@ -3703,7 +3707,7 @@ void OpenGLDriver::blit(
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
|
||||
switch (s->target) {
|
||||
@@ -3729,7 +3733,7 @@ void OpenGLDriver::blit(
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
@@ -3843,7 +3847,7 @@ void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindPipeline(PipelineState state) {
|
||||
void OpenGLDriver::bindPipeline(PipelineState const& state) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
setRasterState(state.rasterState);
|
||||
@@ -3851,6 +3855,7 @@ void OpenGLDriver::bindPipeline(PipelineState state) {
|
||||
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
mValidProgram = useProgram(p);
|
||||
(*mCurrentPushConstants) = p->getPushConstants();
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
@@ -3880,20 +3885,35 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
return;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(instanceCount <= 1)) {
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
|
||||
} else {
|
||||
assert_invariant(!mContext.isES2());
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
|
||||
rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
|
||||
(GLsizei)instanceCount);
|
||||
assert_invariant(!mContext.isES2());
|
||||
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
|
||||
rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
|
||||
(GLsizei)instanceCount);
|
||||
#endif
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
#else
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef FILAMENT_ENABLE_MATDBG
|
||||
assert_invariant(mContext.isES2());
|
||||
assert_invariant(instanceCount == 1);
|
||||
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
|
||||
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
#else
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
@@ -3912,7 +3932,11 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
state.vertexBufferInfo = rp->vbih;
|
||||
bindPipeline(state);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
draw2GLES2(indexOffset, indexCount, instanceCount);
|
||||
} else {
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
@@ -3939,7 +3963,7 @@ void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
|
||||
glDispatchCompute(workGroupCount.x, workGroupCount.y, workGroupCount.z);
|
||||
#endif // BACKEND_OPENGL_LEVEL_GLES31
|
||||
|
||||
#ifdef FILAMENT_ENABLE_MATDBG
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
#else
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "DriverBase.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLTimerQuery.h"
|
||||
#include "GLBufferObject.h"
|
||||
#include "GLTexture.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
@@ -64,9 +66,9 @@ namespace filament::backend {
|
||||
class OpenGLPlatform;
|
||||
class PixelBufferDescriptor;
|
||||
struct TargetBufferInfo;
|
||||
|
||||
class OpenGLProgram;
|
||||
class TimerQueryFactoryInterface;
|
||||
struct PushConstantBundle;
|
||||
|
||||
class OpenGLDriver final : public DriverBase {
|
||||
inline explicit OpenGLDriver(OpenGLPlatform* platform,
|
||||
@@ -92,25 +94,6 @@ public:
|
||||
bool rec709 = false;
|
||||
};
|
||||
|
||||
struct GLBufferObject : public HwBufferObject {
|
||||
using HwBufferObject::HwBufferObject;
|
||||
GLBufferObject(uint32_t size,
|
||||
BufferObjectBinding bindingType, BufferUsage usage) noexcept
|
||||
: HwBufferObject(size), usage(usage), bindingType(bindingType) {
|
||||
}
|
||||
|
||||
struct {
|
||||
GLuint id;
|
||||
union {
|
||||
GLenum binding;
|
||||
void* buffer;
|
||||
};
|
||||
} gl;
|
||||
BufferUsage usage;
|
||||
BufferObjectBinding bindingType;
|
||||
uint16_t age = 0;
|
||||
};
|
||||
|
||||
struct GLVertexBufferInfo : public HwVertexBufferInfo {
|
||||
GLVertexBufferInfo() noexcept = default;
|
||||
GLVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
@@ -140,7 +123,6 @@ public:
|
||||
} gl;
|
||||
};
|
||||
|
||||
struct GLTexture;
|
||||
struct GLSamplerGroup : public HwSamplerGroup {
|
||||
using HwSamplerGroup::HwSamplerGroup;
|
||||
struct Entry {
|
||||
@@ -157,27 +139,9 @@ public:
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
};
|
||||
|
||||
struct GLTexture : public HwTexture {
|
||||
using HwTexture::HwTexture;
|
||||
struct GL {
|
||||
GL() noexcept : imported(false), sidecarSamples(1), reserved(0) {}
|
||||
GLuint id = 0; // texture or renderbuffer id
|
||||
GLenum target = 0;
|
||||
GLenum internalFormat = 0;
|
||||
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
|
||||
using GLBufferObject = filament::backend::GLBufferObject;
|
||||
|
||||
// texture parameters go here too
|
||||
GLfloat anisotropy = 1.0;
|
||||
int8_t baseLevel = 127;
|
||||
int8_t maxLevel = -1;
|
||||
uint8_t targetIndex = 0; // optimization: index corresponding to target
|
||||
bool imported : 1;
|
||||
uint8_t sidecarSamples : 4;
|
||||
uint8_t reserved : 3;
|
||||
} gl;
|
||||
|
||||
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
|
||||
};
|
||||
using GLTexture = filament::backend::GLTexture;
|
||||
|
||||
using GLTimerQuery = filament::backend::GLTimerQuery;
|
||||
|
||||
@@ -268,13 +232,13 @@ private:
|
||||
HandleAllocatorGL mHandleAllocator;
|
||||
|
||||
template<typename D, typename ... ARGS>
|
||||
Handle<D> initHandle(ARGS&& ... args) noexcept {
|
||||
Handle<D> initHandle(ARGS&& ... args) {
|
||||
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args) ...);
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename ... ARGS>
|
||||
typename std::enable_if<std::is_base_of<B, D>::value, D>::type*
|
||||
construct(Handle<B> const& handle, ARGS&& ... args) noexcept {
|
||||
construct(Handle<B> const& handle, ARGS&& ... args) {
|
||||
return mHandleAllocator.destroyAndConstruct<D, B>(handle, std::forward<ARGS>(args) ...);
|
||||
}
|
||||
|
||||
@@ -288,7 +252,7 @@ private:
|
||||
typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B>& handle) noexcept {
|
||||
handle_cast(Handle<B>& handle) {
|
||||
return mHandleAllocator.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
@@ -296,7 +260,7 @@ private:
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B> const& handle) noexcept {
|
||||
handle_cast(Handle<B> const& handle) {
|
||||
return mHandleAllocator.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
@@ -348,22 +312,6 @@ private:
|
||||
void resolvePass(ResolveAction action, GLRenderTarget const* rt,
|
||||
TargetBufferFlags discardFlags) noexcept;
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
|
||||
|
||||
inline GLuint getSampler(SamplerParams sp) const noexcept {
|
||||
assert_invariant(!sp.padding0);
|
||||
assert_invariant(!sp.padding1);
|
||||
assert_invariant(!sp.padding2);
|
||||
auto& samplerMap = mSamplerMap;
|
||||
auto pos = samplerMap.find(sp);
|
||||
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
|
||||
return getSamplerSlow(sp);
|
||||
}
|
||||
return pos->second;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT>& getSamplerBindings() const {
|
||||
return mSamplerBindings;
|
||||
}
|
||||
@@ -388,16 +336,14 @@ private:
|
||||
|
||||
void setScissor(Viewport const& scissor) noexcept;
|
||||
|
||||
void draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount);
|
||||
|
||||
// ES2 only. Uniform buffer emulation binding points
|
||||
GLuint mLastAssignedEmulatedUboId = 0;
|
||||
std::array<std::tuple<GLuint, void const*, uint16_t>, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {};
|
||||
|
||||
// sampler buffer binding points (nullptr if not used)
|
||||
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
|
||||
|
||||
mutable tsl::robin_map<SamplerParams, GLuint,
|
||||
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
|
||||
|
||||
// this must be accessed from the driver thread only
|
||||
std::vector<GLTexture*> mTexturesWithStreamsAttached;
|
||||
|
||||
@@ -431,6 +377,8 @@ private:
|
||||
// for ES2 sRGB support
|
||||
GLSwapChain* mCurrentDrawSwapChain = nullptr;
|
||||
bool mRec709OutputColorspace = false;
|
||||
|
||||
PushConstantBundle* mCurrentPushConstants = nullptr;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -55,6 +55,16 @@ uint32_t OpenGLPlatform::getDefaultFramebufferObject() noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OpenGLPlatform::beginFrame(int64_t monotonic_clock_ns, int64_t refreshIntervalNs,
|
||||
uint32_t frameId) noexcept {
|
||||
}
|
||||
|
||||
void OpenGLPlatform::endFrame(uint32_t frameId) noexcept {
|
||||
}
|
||||
|
||||
void OpenGLPlatform::preCommit() noexcept {
|
||||
}
|
||||
|
||||
OpenGLPlatform::ContextType OpenGLPlatform::getCurrentContextType() const noexcept {
|
||||
return ContextType::UNPROTECTED;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ struct OpenGLProgram::LazyInitializationData {
|
||||
Program::UniformBlockInfo uniformBlockInfo;
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
|
||||
utils::FixedCapacityVector<Program::PushConstant> vertexPushConstants;
|
||||
utils::FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
|
||||
};
|
||||
|
||||
|
||||
@@ -53,7 +55,6 @@ OpenGLProgram::OpenGLProgram() noexcept = default;
|
||||
|
||||
OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
|
||||
: HwProgram(std::move(program.getName())) {
|
||||
|
||||
auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData();
|
||||
lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
|
||||
if (UTILS_UNLIKELY(gld.getContext().isES2())) {
|
||||
@@ -61,6 +62,8 @@ OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
|
||||
} else {
|
||||
lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
|
||||
}
|
||||
lazyInitializationData->vertexPushConstants = std::move(program.getPushConstants(ShaderStage::VERTEX));
|
||||
lazyInitializationData->fragmentPushConstants = std::move(program.getPushConstants(ShaderStage::FRAGMENT));
|
||||
|
||||
ShaderCompilerService& compiler = gld.getShaderCompilerService();
|
||||
mToken = compiler.createProgram(name, std::move(program));
|
||||
@@ -203,6 +206,21 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
}
|
||||
}
|
||||
mUsedBindingsCount = usedBindingCount;
|
||||
|
||||
auto& vertexConstants = lazyInitializationData.vertexPushConstants;
|
||||
auto& fragmentConstants = lazyInitializationData.fragmentPushConstants;
|
||||
|
||||
size_t const totalConstantCount = vertexConstants.size() + fragmentConstants.size();
|
||||
if (totalConstantCount > 0) {
|
||||
mPushConstants.reserve(totalConstantCount);
|
||||
mPushConstantFragmentStageOffset = vertexConstants.size();
|
||||
auto const transformAndAdd = [&](Program::PushConstant const& constant) {
|
||||
GLint const loc = glGetUniformLocation(program, constant.name.c_str());
|
||||
mPushConstants.push_back({loc, constant.type});
|
||||
};
|
||||
std::for_each(vertexConstants.cbegin(), vertexConstants.cend(), transformAndAdd);
|
||||
std::for_each(fragmentConstants.cbegin(), fragmentConstants.cend(), transformAndAdd);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
@@ -38,6 +39,11 @@ namespace filament::backend {
|
||||
|
||||
class OpenGLDriver;
|
||||
|
||||
struct PushConstantBundle {
|
||||
utils::Slice<std::pair<GLint, ConstantType>> vertexConstants;
|
||||
utils::Slice<std::pair<GLint, ConstantType>> fragmentConstants;
|
||||
};
|
||||
|
||||
class OpenGLProgram : public HwProgram {
|
||||
public:
|
||||
|
||||
@@ -70,14 +76,22 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
struct {
|
||||
GLuint program = 0;
|
||||
} gl; // 12 bytes
|
||||
|
||||
// For ES2 only
|
||||
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept;
|
||||
void setRec709ColorSpace(bool rec709) const noexcept;
|
||||
|
||||
struct {
|
||||
GLuint program = 0;
|
||||
} gl; // 4 bytes
|
||||
|
||||
PushConstantBundle getPushConstants() {
|
||||
auto fragBegin = mPushConstants.begin() + mPushConstantFragmentStageOffset;
|
||||
return {
|
||||
.vertexConstants = utils::Slice(mPushConstants.begin(), fragBegin),
|
||||
.fragmentConstants = utils::Slice(fragBegin, mPushConstants.end()),
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
// keep these away from of other class attributes
|
||||
struct LazyInitializationData;
|
||||
@@ -89,14 +103,20 @@ private:
|
||||
|
||||
void updateSamplers(OpenGLDriver* gld) const noexcept;
|
||||
|
||||
ShaderCompilerService::program_token_t mToken{};
|
||||
|
||||
// number of bindings actually used by this program
|
||||
uint8_t mUsedBindingsCount = 0u;
|
||||
UTILS_UNUSED uint8_t padding[3] = {};
|
||||
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
|
||||
|
||||
ShaderCompilerService::program_token_t mToken{}; // 16 bytes
|
||||
|
||||
uint8_t mUsedBindingsCount = 0u; // 1 byte
|
||||
UTILS_UNUSED uint8_t padding[2] = {}; // 2 byte
|
||||
|
||||
// Push constant array offset for fragment stage constants.
|
||||
uint8_t mPushConstantFragmentStageOffset = 0u; // 1 byte
|
||||
|
||||
// only needed for ES2
|
||||
GLint mRec709Location = -1; // 4 bytes
|
||||
|
||||
using LocationInfo = utils::FixedCapacityVector<GLint>;
|
||||
struct UniformsRecord {
|
||||
Program::UniformInfo uniforms;
|
||||
@@ -104,12 +124,15 @@ private:
|
||||
mutable GLuint id = 0;
|
||||
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
|
||||
};
|
||||
UniformsRecord const* mUniformsRecords = nullptr;
|
||||
GLint mRec709Location = -1;
|
||||
UniformsRecord const* mUniformsRecords = nullptr; // 8 bytes
|
||||
|
||||
// Note that this can be replaced with a raw pointer and an uint8_t (for size) to reduce the
|
||||
// size of the container to 9 bytes if there is a need in the future.
|
||||
utils::FixedCapacityVector<std::pair<GLint, ConstantType>> mPushConstants;// 16 bytes
|
||||
};
|
||||
|
||||
// if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket.
|
||||
static_assert(sizeof(OpenGLProgram) <= 64);
|
||||
static_assert(sizeof(OpenGLProgram) <= 64); // currently 64 bytes
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -264,6 +265,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
shaders,
|
||||
token->shaderSourceCode);
|
||||
|
||||
@@ -299,6 +301,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
token->gl.shaders,
|
||||
token->shaderSourceCode);
|
||||
|
||||
@@ -501,6 +504,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
bool multiview,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
|
||||
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
|
||||
|
||||
@@ -514,8 +518,16 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
};
|
||||
|
||||
std::string specializationConstantString;
|
||||
int32_t numViews = 2;
|
||||
for (auto const& sc : specializationConstants) {
|
||||
appendSpecConstantString(specializationConstantString, sc);
|
||||
if (sc.id == 8) {
|
||||
// This constant must match
|
||||
// ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT
|
||||
// which we can't use here because it's defined in EngineEnums.h.
|
||||
// (we're breaking layering here, but it's for the good cause).
|
||||
numViews = std::get<int32_t>(sc.value);
|
||||
}
|
||||
}
|
||||
if (!specializationConstantString.empty()) {
|
||||
specializationConstantString += '\n';
|
||||
@@ -544,17 +556,23 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
|
||||
if (UTILS_LIKELY(!shadersSource[i].empty())) {
|
||||
Program::ShaderBlob& shader = shadersSource[i];
|
||||
char* shader_src = reinterpret_cast<char*>(shader.data());
|
||||
size_t shader_len = shader.size();
|
||||
|
||||
// remove GOOGLE_cpp_style_line_directive
|
||||
std::string_view const source = process_GOOGLE_cpp_style_line_directive(context,
|
||||
reinterpret_cast<char*>(shader.data()), shader.size());
|
||||
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
|
||||
|
||||
// replace the value of layout(num_views = X) for multiview extension
|
||||
if (multiview && stage == ShaderStage::VERTEX) {
|
||||
process_OVR_multiview2(context, numViews, shader_src, shader_len);
|
||||
}
|
||||
|
||||
// add support for ARB_shading_language_packing if needed
|
||||
auto const packingFunctions = process_ARB_shading_language_packing(context);
|
||||
|
||||
// split shader source, so we can insert the specialization constants and the packing
|
||||
// functions
|
||||
auto const [prolog, body] = splitShaderSource(source);
|
||||
auto const [prolog, body] = splitShaderSource({ shader_src, shader_len });
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
prolog.data(),
|
||||
@@ -577,7 +595,7 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
#ifndef NDEBUG
|
||||
// for debugging we return the original shader source (without the modifications we
|
||||
// made here), otherwise the line numbers wouldn't match.
|
||||
outShaderSourceCode[i] = { source.data(), source.length() };
|
||||
outShaderSourceCode[i] = { shader_src, shader_len };
|
||||
#endif
|
||||
|
||||
outShaders[i] = shaderId;
|
||||
@@ -586,15 +604,59 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
}
|
||||
|
||||
// If usages of the Google-style line directive are present, remove them, as some
|
||||
// drivers don't allow the quotation marks. This happens in-place.
|
||||
std::string_view ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
// drivers don't allow the quotation marks. This source modification happens in-place.
|
||||
void ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept {
|
||||
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
||||
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
||||
removeGoogleLineDirectives(source, len); // length is unaffected
|
||||
}
|
||||
}
|
||||
return { source, len };
|
||||
}
|
||||
|
||||
// Look up the `source` to replace the number of eyes for multiview with the given number. This is
|
||||
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
|
||||
// the number of views, which is assumed as a single digit, for multiview.
|
||||
// This source modification happens in-place.
|
||||
void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context,
|
||||
int32_t eyeCount, char* source, size_t len) noexcept {
|
||||
// We don't use regular expression in favor of performance.
|
||||
if (context.ext.OVR_multiview2) {
|
||||
const std::string_view shader{ source, len };
|
||||
const std::string_view layout = "layout";
|
||||
const std::string_view num_views = "num_views";
|
||||
size_t found = 0;
|
||||
while (true) {
|
||||
found = shader.find(layout, found);
|
||||
if (found == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + layout.size());
|
||||
if (found == std::string_view::npos || shader[found] != '(') {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + 1);
|
||||
if (found == std::string_view::npos) {
|
||||
continue;
|
||||
}
|
||||
if (shader.compare(found, num_views.size(), num_views) != 0) {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + num_views.size());
|
||||
if (found == std::string_view::npos || shader[found] != '=') {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + 1);
|
||||
if (found == std::string_view::npos) {
|
||||
continue;
|
||||
}
|
||||
// We assume the value should be one-digit number.
|
||||
assert_invariant(eyeCount < 10);
|
||||
assert_invariant(!::isdigit(source[found + 1]));
|
||||
source[found] = '0' + eyeCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
|
||||
|
||||
@@ -134,10 +134,14 @@ private:
|
||||
OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
bool multiview,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
|
||||
|
||||
static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept;
|
||||
|
||||
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount,
|
||||
char* source, size_t len) noexcept;
|
||||
|
||||
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
||||
|
||||
@@ -174,7 +174,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
};
|
||||
|
||||
#ifdef __ANDROID__
|
||||
bool requestES2Context = false;
|
||||
bool requestES2Context = driverConfig.forceGLES2Context;
|
||||
char property[PROP_VALUE_MAX];
|
||||
int const length = __system_property_get("debug.filament.es2", property);
|
||||
if (length > 0) {
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <android/api-level.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
#include <utils/android/PerformanceHintManager.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Log.h>
|
||||
@@ -36,8 +38,11 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <new>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -112,8 +117,38 @@ void PlatformEGLAndroid::terminate() noexcept {
|
||||
PlatformEGL::terminate();
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::beginFrame(
|
||||
int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs,
|
||||
uint32_t frameId) noexcept {
|
||||
if (mPerformanceHintSession.isValid()) {
|
||||
if (refreshIntervalNs <= 0) {
|
||||
// we're not provided with a target time, assume 16.67ms
|
||||
refreshIntervalNs = 16'666'667;
|
||||
}
|
||||
mStartTimeOfActualWork = clock::time_point(std::chrono::nanoseconds(monotonic_clock_ns));
|
||||
mPerformanceHintSession.updateTargetWorkDuration(refreshIntervalNs);
|
||||
}
|
||||
PlatformEGL::beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
|
||||
}
|
||||
|
||||
void backend::PlatformEGLAndroid::preCommit() noexcept {
|
||||
if (mPerformanceHintSession.isValid()) {
|
||||
auto const actualWorkDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
clock::now() - mStartTimeOfActualWork);
|
||||
mPerformanceHintSession.reportActualWorkDuration(actualWorkDuration.count());
|
||||
}
|
||||
PlatformEGL::preCommit();
|
||||
}
|
||||
|
||||
Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
|
||||
// the refresh rate default value doesn't matter, we change it later
|
||||
int32_t const tid = gettid();
|
||||
mPerformanceHintSession = PerformanceHintManager::Session{
|
||||
mPerformanceHintManager, &tid, 1, 16'666'667 };
|
||||
|
||||
Driver* driver = PlatformEGL::createDriver(sharedContext, driverConfig);
|
||||
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
|
||||
|
||||
|
||||
@@ -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 << "}";
|
||||
}
|
||||
|
||||
@@ -26,21 +26,16 @@
|
||||
|
||||
#include <smolv.h>
|
||||
|
||||
#include "generated/vkshaders/vkshaders.h"
|
||||
|
||||
using namespace bluevk;
|
||||
using namespace utils;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using ImgUtil = VulkanImageUtility;
|
||||
|
||||
namespace {
|
||||
|
||||
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
|
||||
VulkanAttachment src, VulkanAttachment dst,
|
||||
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< " layout=" << src.getLayout()
|
||||
@@ -48,21 +43,8 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
}
|
||||
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = src.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = src.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
const VkImageSubresourceRange dstRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = dst.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = dst.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
VkImageSubresourceRange const srcRange = src.getSubresourceRange();
|
||||
VkImageSubresourceRange const dstRange = dst.getSubresourceRange();
|
||||
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
@@ -77,15 +59,15 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
.dstOffsets = { dstRect[0], dstRect[1] },
|
||||
}};
|
||||
vkCmdBlitImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
src.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, blitRegions, filter);
|
||||
|
||||
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
|
||||
oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
|
||||
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
|
||||
}
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
|
||||
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
|
||||
}
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
|
||||
@@ -93,7 +75,6 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
|
||||
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
VulkanAttachment src, VulkanAttachment dst) {
|
||||
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< " layout=" << src.getLayout()
|
||||
@@ -101,21 +82,8 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
}
|
||||
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = src.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = src.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
const VkImageSubresourceRange dstRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = dst.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = dst.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
VkImageSubresourceRange const srcRange = src.getSubresourceRange();
|
||||
VkImageSubresourceRange const dstRange = dst.getSubresourceRange();
|
||||
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
@@ -133,15 +101,15 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe
|
||||
.extent = { src.getExtent2D().width, src.getExtent2D().height, 1 },
|
||||
}};
|
||||
vkCmdResolveImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
src.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, resolveRegions);
|
||||
|
||||
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
|
||||
oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
|
||||
oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage);
|
||||
}
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
|
||||
oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage);
|
||||
}
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANBLITTER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANBLITTER_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
@@ -27,8 +27,8 @@ VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
VkBufferUsageFlags usage, uint32_t numBytes)
|
||||
: mAllocator(allocator),
|
||||
mStagePool(stagePool),
|
||||
mUsage(usage) {
|
||||
|
||||
mUsage(usage),
|
||||
mUpdatedBytes(0) {
|
||||
// for now make sure that only 1 bit is set in usage
|
||||
// (because loadFromCpu() assumes that somewhat)
|
||||
assert_invariant(usage && !(usage & (usage - 1)));
|
||||
@@ -49,8 +49,7 @@ VulkanBuffer::~VulkanBuffer() {
|
||||
}
|
||||
|
||||
void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
|
||||
uint32_t numBytes) const {
|
||||
assert_invariant(byteOffset == 0);
|
||||
uint32_t numBytes) {
|
||||
VulkanStage const* stage = mStagePool.acquireStage(numBytes);
|
||||
void* mapped;
|
||||
vmaMapMemory(mAllocator, stage->memory, &mapped);
|
||||
@@ -58,15 +57,51 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
|
||||
vmaUnmapMemory(mAllocator, stage->memory);
|
||||
vmaFlushAllocation(mAllocator, stage->memory, byteOffset, numBytes);
|
||||
|
||||
VkBufferCopy region{ .size = numBytes };
|
||||
// If there was a previous update, then we need to make sure the following write is properly
|
||||
// synced with the previous read.
|
||||
if (mUpdatedBytes > 0) {
|
||||
VkAccessFlags srcAccess = 0;
|
||||
VkPipelineStageFlags srcStage = 0;
|
||||
if (mUsage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
|
||||
srcAccess = VK_ACCESS_SHADER_READ_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
} else if (mUsage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) {
|
||||
srcAccess = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||
} else if (mUsage & VK_BUFFER_USAGE_INDEX_BUFFER_BIT) {
|
||||
srcAccess = VK_ACCESS_INDEX_READ_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||
}
|
||||
|
||||
VkBufferMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.srcAccessMask = srcAccess,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = mGpuBuffer,
|
||||
.size = mUpdatedBytes,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuf, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
|
||||
&barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
VkBufferCopy region {
|
||||
.srcOffset = 0,
|
||||
.dstOffset = byteOffset,
|
||||
.size = numBytes,
|
||||
};
|
||||
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, ®ion);
|
||||
|
||||
mUpdatedBytes = numBytes;
|
||||
|
||||
// Firstly, ensure that the copy finishes before the next draw call.
|
||||
// Secondly, in case the user decides to upload another chunk (without ever using the first one)
|
||||
// we need to ensure that this upload completes first (hence
|
||||
// dstStageMask=VK_PIPELINE_STAGE_TRANSFER_BIT).
|
||||
VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
|
||||
if (mUsage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) {
|
||||
dstAccessMask |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
|
||||
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||
@@ -75,26 +110,24 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
|
||||
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||
} else if (mUsage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
|
||||
dstAccessMask |= VK_ACCESS_UNIFORM_READ_BIT;
|
||||
// NOTE: ideally dstStageMask would include VERTEX_SHADER_BIT | FRAGMENT_SHADER_BIT, but
|
||||
// this seems to be insufficient on Mali devices. To work around this we are using a more
|
||||
// aggressive ALL_GRAPHICS_BIT barrier.
|
||||
dstStageMask |= VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
|
||||
dstStageMask |=
|
||||
(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
} else if (mUsage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) {
|
||||
// TODO: implement me
|
||||
}
|
||||
|
||||
VkBufferMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = dstAccessMask,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = mGpuBuffer,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = dstAccessMask,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = mGpuBuffer,
|
||||
.size = VK_WHOLE_SIZE,
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0, nullptr, 1,
|
||||
&barrier, 0, nullptr);
|
||||
&barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -29,7 +30,7 @@ public:
|
||||
uint32_t numBytes);
|
||||
~VulkanBuffer();
|
||||
void loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
|
||||
uint32_t numBytes) const;
|
||||
uint32_t numBytes);
|
||||
VkBuffer getGpuBuffer() const {
|
||||
return mGpuBuffer;
|
||||
}
|
||||
@@ -41,6 +42,7 @@ private:
|
||||
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
|
||||
VkBuffer mGpuBuffer = VK_NULL_HANDLE;
|
||||
VkBufferUsageFlags mUsage = {};
|
||||
uint32_t mUpdatedBytes = 0;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -47,7 +47,8 @@ VulkanCmdFence::VulkanCmdFence(VkFence ifence)
|
||||
|
||||
VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device,
|
||||
VkCommandPool pool)
|
||||
: mResourceManager(allocator) {
|
||||
: mResourceManager(allocator),
|
||||
mPipeline(VK_NULL_HANDLE) {
|
||||
// Create the low-level command buffer.
|
||||
const VkCommandBufferAllocateInfo allocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
|
||||
@@ -89,6 +89,15 @@ struct VulkanCommandBuffer {
|
||||
inline void reset() {
|
||||
fence.reset();
|
||||
mResourceManager.clear();
|
||||
mPipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
inline void setPipeline(VkPipeline pipeline) {
|
||||
mPipeline = pipeline;
|
||||
}
|
||||
|
||||
inline VkPipeline pipeline() const {
|
||||
return mPipeline;
|
||||
}
|
||||
|
||||
inline VkCommandBuffer buffer() const {
|
||||
@@ -103,6 +112,7 @@ struct VulkanCommandBuffer {
|
||||
private:
|
||||
VulkanAcquireOnlyResourceManager mResourceManager;
|
||||
VkCommandBuffer mBuffer;
|
||||
VkPipeline mPipeline;
|
||||
};
|
||||
|
||||
// Allows classes to be notified after a new command buffer has been activated.
|
||||
|
||||
@@ -43,9 +43,12 @@
|
||||
// Enables Android systrace
|
||||
#define FVK_DEBUG_SYSTRACE 0x00000001
|
||||
|
||||
// Group markers are used to denote collection of GPU commands. It is typically at the granualarity
|
||||
// of a renderpass.
|
||||
// Group markers are used to denote collections of GPU commands. It is typically at the
|
||||
// granualarity of a renderpass. You can enable this along with FVK_DEBUG_DEBUG_UTILS to take
|
||||
// advantage of vkCmdBegin/EndDebugUtilsLabelEXT. You can also just enable this with
|
||||
// FVK_DEBUG_PRINT_GROUP_MARKERS to print the current marker to stdout.
|
||||
#define FVK_DEBUG_GROUP_MARKERS 0x00000002
|
||||
|
||||
#define FVK_DEBUG_TEXTURE 0x00000004
|
||||
#define FVK_DEBUG_LAYOUT_TRANSITION 0x00000008
|
||||
#define FVK_DEBUG_COMMAND_BUFFER 0x00000010
|
||||
@@ -63,25 +66,23 @@
|
||||
// Enable the debug utils extension if it is available.
|
||||
#define FVK_DEBUG_DEBUG_UTILS 0x00008000
|
||||
|
||||
// Use this to debug potential Handle/Resource leakage. It will print out reference counts for all
|
||||
// the currently active resources.
|
||||
#define FVK_DEBUG_RESOURCE_LEAK 0x00010000
|
||||
|
||||
// Usefaul default combinations
|
||||
// Useful default combinations
|
||||
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
|
||||
#define FVK_DEBUG_PERFORMANCE \
|
||||
FVK_DEBUG_SYSTRACE
|
||||
|
||||
#define FVK_DEBUG_CORRECTNESS \
|
||||
FVK_DEBUG_VALIDATION | \
|
||||
FVK_DEBUG_SHADER_MODULE | \
|
||||
FVK_DEBUG_TEXTURE | \
|
||||
FVK_DEBUG_LAYOUT_TRANSITION
|
||||
|
||||
#define FVK_DEBUG_RENDER_PASSES \
|
||||
FVK_DEBUG_GROUP_MARKERS | \
|
||||
FVK_DEBUG_PRINT_GROUP_MARKERS
|
||||
#if defined(FILAMENT_BACKEND_DEBUG_FLAG)
|
||||
#define FVK_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FVK_DEBUG_EVERYTHING)
|
||||
#else
|
||||
#define FVK_DEBUG_FORWARDED_FLAG 0
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE)
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG)
|
||||
#else
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#endif
|
||||
@@ -107,7 +108,7 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
// end dependcy checks
|
||||
|
||||
// Shorthand for combination of enabled debug flags
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) && FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 1
|
||||
#else
|
||||
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 0
|
||||
|
||||
@@ -57,15 +57,19 @@ VkExtent2D VulkanAttachment::getExtent2D() const {
|
||||
return { std::max(1u, texture->width >> level), std::max(1u, texture->height >> level) };
|
||||
}
|
||||
|
||||
VkImageView VulkanAttachment::getImageView(VkImageAspectFlags aspect) {
|
||||
VkImageView VulkanAttachment::getImageView() {
|
||||
assert_invariant(texture);
|
||||
return texture->getAttachmentView(getSubresourceRange(aspect));
|
||||
return texture->getAttachmentView(getSubresourceRange());
|
||||
}
|
||||
|
||||
VkImageSubresourceRange VulkanAttachment::getSubresourceRange(VkImageAspectFlags aspect) const {
|
||||
bool VulkanAttachment::isDepth() const {
|
||||
return texture->getImageAspect() & VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
|
||||
VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
|
||||
assert_invariant(texture);
|
||||
return {
|
||||
.aspectMask = aspect,
|
||||
.aspectMask = texture->getImageAspect(),
|
||||
.baseMipLevel = uint32_t(level),
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = uint32_t(layer),
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <utils/bitset.h>
|
||||
@@ -42,16 +41,18 @@ struct VulkanTimerQuery;
|
||||
struct VulkanCommandBuffer;
|
||||
|
||||
struct VulkanAttachment {
|
||||
VulkanTexture* texture;
|
||||
VulkanTexture* texture = nullptr;
|
||||
uint8_t level = 0;
|
||||
uint16_t layer = 0;
|
||||
|
||||
bool isDepth() const;
|
||||
VkImage getImage() const;
|
||||
VkFormat getFormat() const;
|
||||
VulkanLayout getLayout() const;
|
||||
VkExtent2D getExtent2D() const;
|
||||
VkImageView getImageView(VkImageAspectFlags aspect);
|
||||
VkImageView getImageView();
|
||||
// TODO: maybe embed aspect into the attachment or texture itself.
|
||||
VkImageSubresourceRange getSubresourceRange(VkImageAspectFlags aspect) const;
|
||||
VkImageSubresourceRange getSubresourceRange() const;
|
||||
};
|
||||
|
||||
class VulkanTimestamps {
|
||||
@@ -102,8 +103,12 @@ public:
|
||||
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
||||
}
|
||||
|
||||
inline VkFormatList const& getAttachmentDepthFormats() const {
|
||||
return mDepthFormats;
|
||||
inline VkFormatList const& getAttachmentDepthStencilFormats() const {
|
||||
return mDepthStencilFormats;
|
||||
}
|
||||
|
||||
inline VkFormatList const& getBlittableDepthStencilFormats() const {
|
||||
return mBlittableDepthStencilFormats;
|
||||
}
|
||||
|
||||
inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept {
|
||||
@@ -132,7 +137,8 @@ private:
|
||||
bool mDebugMarkersSupported = false;
|
||||
bool mDebugUtilsSupported = false;
|
||||
|
||||
VkFormatList mDepthFormats;
|
||||
VkFormatList mDepthStencilFormats;
|
||||
VkFormatList mBlittableDepthStencilFormats;
|
||||
|
||||
// For convenience so that VulkanPlatform can initialize the private fields.
|
||||
friend class VulkanPlatform;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "vulkan/VulkanDriver.h"
|
||||
#include "VulkanDriver.h"
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
#include "DataReshaper.h"
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanDriverFactory.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanTexture.h"
|
||||
|
||||
@@ -102,6 +101,15 @@ VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevi
|
||||
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,
|
||||
@@ -197,8 +205,6 @@ void DebugUtils::setName(VkObjectType type, uint64_t handle, char const* name) {
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
using ImgUtil = VulkanImageUtility;
|
||||
|
||||
Dispatcher VulkanDriver::getDispatcher() const noexcept {
|
||||
return ConcreteDispatcher<VulkanDriver>::make();
|
||||
}
|
||||
@@ -214,12 +220,14 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mThreadSafeResourceManager(&mResourceAllocator),
|
||||
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
|
||||
mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator),
|
||||
mPipelineCache(&mResourceAllocator),
|
||||
mPipelineLayoutCache(mPlatform->getDevice(), &mResourceAllocator),
|
||||
mPipelineCache(mPlatform->getDevice(), mAllocator),
|
||||
mStagePool(mAllocator, &mCommands),
|
||||
mFramebufferCache(mPlatform->getDevice()),
|
||||
mSamplerCache(mPlatform->getDevice()),
|
||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mDescriptorSetManager(mPlatform->getDevice(), &mResourceAllocator),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
@@ -243,13 +251,17 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
#endif
|
||||
|
||||
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
|
||||
mCommands.setObserver(&mPipelineCache);
|
||||
mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator);
|
||||
|
||||
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
|
||||
mContext, mAllocator, &mCommands, mStagePool);
|
||||
mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands);
|
||||
|
||||
mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView());
|
||||
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
|
||||
mEmptyBufferObject);
|
||||
|
||||
mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts, VulkanProgram* program) {
|
||||
return mPipelineLayoutCache.getLayout(layouts, program);
|
||||
};
|
||||
}
|
||||
|
||||
VulkanDriver::~VulkanDriver() noexcept = default;
|
||||
@@ -310,13 +322,14 @@ ShaderModel VulkanDriver::getShaderModel() const noexcept {
|
||||
}
|
||||
|
||||
void VulkanDriver::terminate() {
|
||||
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();
|
||||
|
||||
delete mEmptyTexture;
|
||||
mResourceManager.clear();
|
||||
|
||||
mTimestamps.reset();
|
||||
|
||||
mBlitter.terminate();
|
||||
@@ -329,6 +342,12 @@ void VulkanDriver::terminate() {
|
||||
mPipelineCache.terminate();
|
||||
mFramebufferCache.reset();
|
||||
mSamplerCache.terminate();
|
||||
mDescriptorSetManager.terminate();
|
||||
mPipelineLayoutCache.terminate();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
mResourceAllocator.print();
|
||||
#endif
|
||||
|
||||
vmaDestroyAllocator(mAllocator);
|
||||
|
||||
@@ -360,6 +379,8 @@ void VulkanDriver::collectGarbage() {
|
||||
mCommands.gc();
|
||||
mStagePool.gc();
|
||||
mFramebufferCache.gc();
|
||||
mPipelineCache.gc();
|
||||
mDescriptorSetManager.gc();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
mResourceAllocator.print();
|
||||
@@ -367,16 +388,17 @@ void VulkanDriver::collectGarbage() {
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
}
|
||||
|
||||
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
@@ -489,9 +511,6 @@ void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
||||
return;
|
||||
}
|
||||
auto bufferObject = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
if (bufferObject->bindingType == BufferObjectBinding::UNIFORM) {
|
||||
mPipelineCache.unbindUniformBuffer(bufferObject->buffer.getGpuBuffer());
|
||||
}
|
||||
mResourceManager.release(bufferObject);
|
||||
}
|
||||
|
||||
@@ -542,6 +561,7 @@ void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
return;
|
||||
}
|
||||
auto vkprogram = mResourceAllocator.handle_cast<VulkanProgram*>(ph);
|
||||
mDescriptorSetManager.clearProgram(vkprogram);
|
||||
mResourceManager.release(vkprogram);
|
||||
}
|
||||
|
||||
@@ -898,6 +918,11 @@ bool VulkanDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
|
||||
auto const& formats = mContext.getBlittableDepthStencilFormats();
|
||||
return std::find(formats.begin(), formats.end(), getVkFormat(format)) != formats.end();
|
||||
}
|
||||
|
||||
bool VulkanDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
@@ -1211,12 +1236,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
}
|
||||
|
||||
VulkanLayout const currentDepthLayout = depth.getLayout();
|
||||
VulkanLayout const renderPassDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
// We need to keep the final layout as an attachment because the implicit transition does not
|
||||
// have any barrier guarrantees, meaning that if we want to sample from the output in the next
|
||||
// pass, then we'd have a race-condition/validation error.
|
||||
VulkanLayout const finalDepthLayout = renderPassDepthLayout;
|
||||
VulkanLayout currentDepthLayout = depth.getLayout();
|
||||
|
||||
TargetBufferFlags clearVal = params.flags.clear;
|
||||
TargetBufferFlags discardEndVal = params.flags.discardEnd;
|
||||
@@ -1225,16 +1245,20 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
discardEndVal &= ~TargetBufferFlags::DEPTH;
|
||||
clearVal &= ~TargetBufferFlags::DEPTH;
|
||||
}
|
||||
auto const attachmentSubresourceRange = depth.getSubresourceRange(VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
depth.texture->setLayout(attachmentSubresourceRange, VulkanLayout::DEPTH_ATTACHMENT);
|
||||
// If the depth attachment texture was previously sampled, then we need to manually
|
||||
// transition it to an attachment. This is necessary to also set up a barrier between the
|
||||
// previous read and the potentially coming write.
|
||||
if (currentDepthLayout == VulkanLayout::DEPTH_SAMPLER) {
|
||||
depth.texture->transitionLayout(cmdbuffer, depth.getSubresourceRange(),
|
||||
VulkanLayout::DEPTH_ATTACHMENT);
|
||||
currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the VkRenderPass or fetch it from cache.
|
||||
VulkanFboCache::RenderPassKey rpkey = {
|
||||
.initialColorLayoutMask = 0,
|
||||
.initialDepthLayout = currentDepthLayout,
|
||||
.renderPassDepthLayout = renderPassDepthLayout,
|
||||
.finalDepthLayout = finalDepthLayout,
|
||||
.depthFormat = depth.getFormat(),
|
||||
.clear = clearVal,
|
||||
.discardStart = discardStart,
|
||||
@@ -1251,9 +1275,9 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
rpkey.needsResolveMask |= (1 << i);
|
||||
}
|
||||
if (info.texture->getPrimaryImageLayout() != VulkanLayout::COLOR_ATTACHMENT) {
|
||||
((VulkanTexture*) info.texture)->transitionLayout(cmdbuffer,
|
||||
info.getSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT),
|
||||
VulkanLayout::COLOR_ATTACHMENT);
|
||||
((VulkanTexture*) info.texture)
|
||||
->transitionLayout(cmdbuffer, info.getSubresourceRange(),
|
||||
VulkanLayout::COLOR_ATTACHMENT);
|
||||
}
|
||||
} else {
|
||||
rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED;
|
||||
@@ -1271,27 +1295,42 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
.layers = 1,
|
||||
.samples = rpkey.samples,
|
||||
};
|
||||
auto& renderPassAttachments = mRenderPassFboInfo.attachments;
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (!rt->getColor(i).texture) {
|
||||
fbkey.color[i] = VK_NULL_HANDLE;
|
||||
fbkey.resolve[i] = VK_NULL_HANDLE;
|
||||
} else if (fbkey.samples == 1) {
|
||||
fbkey.color[i] = rt->getColor(i).getImageView(VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
auto& colorAttachment = rt->getColor(i);
|
||||
renderPassAttachments.insert(colorAttachment);
|
||||
fbkey.color[i] = colorAttachment.getImageView();
|
||||
fbkey.resolve[i] = VK_NULL_HANDLE;
|
||||
assert_invariant(fbkey.color[i]);
|
||||
} else {
|
||||
fbkey.color[i] = rt->getMsaaColor(i).getImageView(VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
VulkanTexture* texture = (VulkanTexture*) rt->getColor(i).texture;
|
||||
auto& msaaColorAttachment = rt->getMsaaColor(i);
|
||||
renderPassAttachments.insert(msaaColorAttachment);
|
||||
|
||||
auto& colorAttachment = rt->getColor(i);
|
||||
fbkey.color[i] = msaaColorAttachment.getImageView();
|
||||
|
||||
VulkanTexture* texture = colorAttachment.texture;
|
||||
if (texture->samples == 1) {
|
||||
fbkey.resolve[i] = rt->getColor(i).getImageView(VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
mRenderPassFboInfo.hasColorResolve = true;
|
||||
|
||||
renderPassAttachments.insert(colorAttachment);
|
||||
fbkey.resolve[i] = colorAttachment.getImageView();
|
||||
assert_invariant(fbkey.resolve[i]);
|
||||
}
|
||||
assert_invariant(fbkey.color[i]);
|
||||
}
|
||||
}
|
||||
if (depth.texture) {
|
||||
fbkey.depth = depth.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
fbkey.depth = depth.getImageView();
|
||||
assert_invariant(fbkey.depth);
|
||||
renderPassAttachments.insert(depth);
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE bool const depthDiscardEnd =
|
||||
any(rpkey.discardEnd & TargetBufferFlags::DEPTH);
|
||||
|
||||
// Vulkan 1.1 does not support multisampled depth resolve, so let's check here
|
||||
// and assert if this is requested. (c.f. isAutoDepthResolveSupported)
|
||||
@@ -1300,7 +1339,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
// - If the RT is MS then all SS attachments are auto resolved if not discarded.
|
||||
assert_invariant(!(rt->getSamples() > 1 &&
|
||||
rt->getDepth().texture->samples == 1 &&
|
||||
!any(rpkey.discardEnd & TargetBufferFlags::DEPTH)));
|
||||
!depthDiscardEnd));
|
||||
}
|
||||
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
|
||||
@@ -1313,16 +1352,10 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
#endif
|
||||
|
||||
// The current command buffer now owns a reference to the render target and its attachments.
|
||||
// Note that we must acquire parent textures, not sidecars.
|
||||
// The current command buffer now has references to the render target and its attachments.
|
||||
commands.acquire(rt);
|
||||
if (depth.texture) {
|
||||
commands.acquire((VulkanTexture*) depth.texture);
|
||||
}
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (rt->getColor(i).texture) {
|
||||
commands.acquire(rt->getColor(i).texture);
|
||||
}
|
||||
for (auto const& attachment: renderPassAttachments) {
|
||||
commands.acquire(attachment.texture);
|
||||
}
|
||||
|
||||
// Populate the structures required for vkCmdBeginRenderPass.
|
||||
@@ -1408,33 +1441,52 @@ void VulkanDriver::endRenderPass(int) {
|
||||
// issue several of them when considering MRT. This would be very complex to set up and would
|
||||
// require more state tracking, so we've chosen to use a memory barrier for simplicity and
|
||||
// correctness.
|
||||
|
||||
// NOTE: ideally dstStageMask would merely be VERTEX_SHADER_BIT | FRAGMENT_SHADER_BIT, but this
|
||||
// seems to be insufficient on Mali devices. To work around this we are adding a more aggressive
|
||||
// TOP_OF_PIPE barrier.
|
||||
if (!rt->isSwapChain()) {
|
||||
VkMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
};
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
if (rt->hasDepth()) {
|
||||
barrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
srcStageMask |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
for (auto const& attachment: mRenderPassFboInfo.attachments) {
|
||||
bool const isDepth = attachment.isDepth();
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
|
||||
// This is a workaround around a validation issue (might not be an actual driver issue).
|
||||
if (mRenderPassFboInfo.hasColorResolve && !isDepth) {
|
||||
srcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
|
||||
}
|
||||
|
||||
VkPipelineStageFlags dstStageMask =
|
||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
VkAccessFlags srcAccess = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
VkAccessFlags dstAccess = VK_ACCESS_SHADER_READ_BIT;
|
||||
VulkanLayout layout = VulkanFboCache::FINAL_COLOR_ATTACHMENT_LAYOUT;
|
||||
if (isDepth) {
|
||||
srcAccess = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
dstAccess = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
|
||||
srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||
layout = VulkanFboCache::FINAL_DEPTH_ATTACHMENT_LAYOUT;
|
||||
}
|
||||
|
||||
auto const vkLayout = imgutil::getVkLayout(layout);
|
||||
auto const& range = attachment.getSubresourceRange();
|
||||
VkImageMemoryBarrier barrier = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = srcAccess,
|
||||
.dstAccessMask = dstAccess,
|
||||
.oldLayout = vkLayout,
|
||||
.newLayout = vkLayout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = attachment.getImage(),
|
||||
.subresourceRange = range,
|
||||
};
|
||||
|
||||
attachment.texture->setLayout(range, layout);
|
||||
vkCmdPipelineBarrier(cmdbuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr,
|
||||
1, &barrier);
|
||||
}
|
||||
vkCmdPipelineBarrier(cmdbuffer, srcStageMask,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | // <== For Mali
|
||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
if (mCurrentRenderPass.currentSubpass > 0) {
|
||||
for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) {
|
||||
mPipelineCache.bindInputAttachment(i, {});
|
||||
}
|
||||
mCurrentRenderPass.currentSubpass = 0;
|
||||
}
|
||||
mRenderPassFboInfo.clear();
|
||||
mDescriptorSetManager.clearState();
|
||||
mCurrentRenderPass.renderTarget = nullptr;
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
FVK_SYSTRACE_END();
|
||||
@@ -1453,15 +1505,9 @@ void VulkanDriver::nextSubpass(int) {
|
||||
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
|
||||
++mCurrentRenderPass.currentSubpass);
|
||||
|
||||
for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) {
|
||||
if ((1 << i) & mCurrentRenderPass.params.subpassMask) {
|
||||
VulkanAttachment subpassInput = renderTarget->getColor(i);
|
||||
VkDescriptorImageInfo info = {
|
||||
.imageView = subpassInput.getImageView(VK_IMAGE_ASPECT_COLOR_BIT),
|
||||
.imageLayout = ImgUtil::getVkLayout(subpassInput.getLayout()),
|
||||
};
|
||||
mPipelineCache.bindInputAttachment(i, info);
|
||||
}
|
||||
if (mCurrentRenderPass.params.subpassMask & 0x1) {
|
||||
VulkanAttachment subpassInput = renderTarget->getColor(0);
|
||||
mDescriptorSetManager.updateInputAttachment({}, subpassInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1501,25 +1547,24 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||
|
||||
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
|
||||
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
const VkDeviceSize offset = 0;
|
||||
const VkDeviceSize size = VK_WHOLE_SIZE;
|
||||
mPipelineCache.bindUniformBufferObject((uint32_t) index, bo, offset, size);
|
||||
VkDeviceSize const offset = 0;
|
||||
VkDeviceSize const size = VK_WHOLE_SIZE;
|
||||
mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size);
|
||||
}
|
||||
|
||||
void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
|
||||
Handle<HwBufferObject> boh, uint32_t offset, uint32_t size) {
|
||||
|
||||
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
|
||||
bindingType == BufferObjectBinding::UNIFORM);
|
||||
assert_invariant(bindingType == BufferObjectBinding::UNIFORM);
|
||||
|
||||
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
|
||||
|
||||
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
mPipelineCache.bindUniformBufferObject((uint32_t)index, bo, offset, size);
|
||||
mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size);
|
||||
}
|
||||
|
||||
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
|
||||
// TODO: implement unbindBuffer()
|
||||
mDescriptorSetManager.clearBuffer((uint32_t) index);
|
||||
}
|
||||
|
||||
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
@@ -1527,6 +1572,14 @@ void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
mSamplerBindings[index] = hwsb;
|
||||
}
|
||||
|
||||
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
|
||||
VulkanCommands* commands = &mCommands;
|
||||
mBoundPipeline.program->writePushConstant(commands, mBoundPipeline.pipelineLayout, stage, index,
|
||||
value);
|
||||
}
|
||||
|
||||
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands.insertEventMarker(string, len);
|
||||
@@ -1710,7 +1763,7 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw");
|
||||
|
||||
@@ -1767,22 +1820,14 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
|
||||
// Filament concept used to form groups of samplers.
|
||||
|
||||
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||
|
||||
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
|
||||
VulkanPipelineCache::UsageFlags usage = program->getUsage();
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
auto const& bindingToName = program->getBindingToName();
|
||||
#endif
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) {
|
||||
for (auto binding: program->getBindings()) {
|
||||
uint16_t const indexPair = bindingToSamplerIndex[binding];
|
||||
|
||||
if (indexPair == 0xffff) {
|
||||
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1791,24 +1836,20 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
|
||||
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd];
|
||||
if (!vksb) {
|
||||
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
|
||||
continue;
|
||||
}
|
||||
SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd;
|
||||
|
||||
if (UTILS_UNLIKELY(!boundSampler->t)) {
|
||||
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
|
||||
continue;
|
||||
}
|
||||
|
||||
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
|
||||
// TODO: can this uninitialized check be checked in a higher layer?
|
||||
// This fallback path is very flaky because the dummy texture might not have
|
||||
// matching characteristics. (e.g. if the missing texture is a 3D texture)
|
||||
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE) && FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
|
||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||
utils::slog.w << " at binding point " << +binding << utils::io::endl;
|
||||
@@ -1816,43 +1857,28 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
texture = mEmptyTexture;
|
||||
}
|
||||
|
||||
SamplerParams const& samplerParams = boundSampler->s;
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(samplerParams);
|
||||
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(boundSampler->s);
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER,
|
||||
reinterpret_cast<uint64_t>(vksampler), bindingToName[binding].c_str());
|
||||
#endif
|
||||
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
|
||||
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.
|
||||
imageView = texture->getAttachmentView(range);
|
||||
} else {
|
||||
imageView = texture->getViewForType(range, expectedType);
|
||||
}
|
||||
|
||||
samplerInfo[binding] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = imageView,
|
||||
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
|
||||
};
|
||||
samplerTextures[binding] = texture;
|
||||
mDescriptorSetManager.updateSampler({}, binding, texture, vksampler);
|
||||
}
|
||||
|
||||
mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
|
||||
auto const pipelineLayout = mDescriptorSetManager.bind(commands, program, mGetPipelineFunction);
|
||||
mBoundPipeline = {
|
||||
.program = program,
|
||||
.pipelineLayout = pipelineLayout,
|
||||
};
|
||||
|
||||
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});
|
||||
|
||||
// Bind a new pipeline if the pipeline state changed.
|
||||
// If allocation failed, skip the draw call and bail. We do not emit an error since the
|
||||
// validation layer will already do so.
|
||||
if (!mPipelineCache.bindPipeline(commands)) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
@@ -1892,12 +1918,8 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
VulkanCommandBuffer& commands = mCommands.get();
|
||||
VkCommandBuffer cmdbuffer = commands.buffer();
|
||||
|
||||
// Bind new descriptor sets if they need to change.
|
||||
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
||||
// message since the validation layers already do so.
|
||||
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
|
||||
return;
|
||||
}
|
||||
// Bind "dynamic" UBOs if they need to change.
|
||||
mDescriptorSetManager.dynamicBind(&commands, {});
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
const uint32_t firstIndex = indexOffset;
|
||||
@@ -1947,7 +1969,7 @@ void VulkanDriver::scissor(Viewport scissorBox) {
|
||||
|
||||
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
rt->transformClientRectToPlatform(&scissor);
|
||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
|
||||
}
|
||||
|
||||
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "caching/VulkanDescriptorSetManager.h"
|
||||
#include "caching/VulkanPipelineLayoutCache.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include "private/backend/Driver.h"
|
||||
@@ -40,6 +42,25 @@ namespace filament::backend {
|
||||
class VulkanPlatform;
|
||||
struct VulkanSamplerGroup;
|
||||
|
||||
// The maximum number of attachments for any renderpass (color + resolve + depth)
|
||||
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,
|
||||
@@ -70,6 +91,8 @@ public:
|
||||
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
private:
|
||||
static constexpr uint8_t MAX_SAMPLER_BINDING_COUNT = Program::SAMPLER_BINDING_COUNT;
|
||||
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||
const char* methodName) noexcept override;
|
||||
|
||||
@@ -106,7 +129,9 @@ private:
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
std::unique_ptr<VulkanTimestamps> mTimestamps;
|
||||
|
||||
// Placeholder resources
|
||||
VulkanTexture* mEmptyTexture;
|
||||
VulkanBufferObject* mEmptyBufferObject;
|
||||
|
||||
VulkanSwapChain* mCurrentSwapChain = nullptr;
|
||||
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
|
||||
@@ -123,13 +148,26 @@ private:
|
||||
VulkanThreadSafeResourceManager mThreadSafeResourceManager;
|
||||
|
||||
VulkanCommands mCommands;
|
||||
VulkanPipelineLayoutCache mPipelineLayoutCache;
|
||||
VulkanPipelineCache mPipelineCache;
|
||||
VulkanStagePool mStagePool;
|
||||
VulkanFboCache mFramebufferCache;
|
||||
VulkanSamplerCache mSamplerCache;
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
VulkanSamplerGroup* mSamplerBindings[MAX_SAMPLER_BINDING_COUNT] = {};
|
||||
VulkanReadPixels mReadPixels;
|
||||
VulkanDescriptorSetManager mDescriptorSetManager;
|
||||
|
||||
VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
struct BoundPipeline {
|
||||
VulkanProgram* program;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
};
|
||||
BoundPipeline mBoundPipeline = {};
|
||||
|
||||
RenderPassFboBundle mRenderPassFboInfo;
|
||||
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
};
|
||||
|
||||
@@ -29,14 +29,10 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using ImgUtil = VulkanImageUtility;
|
||||
|
||||
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;
|
||||
if (k1.renderPassDepthLayout != k2.renderPassDepthLayout) return false;
|
||||
if (k1.finalDepthLayout != k2.finalDepthLayout) return false;
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (k1.colorFormat[i] != k2.colorFormat[i]) return false;
|
||||
}
|
||||
@@ -197,7 +193,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) {
|
||||
continue;
|
||||
}
|
||||
const VkImageLayout subpassLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT);
|
||||
const VkImageLayout subpassLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT);
|
||||
uint32_t index;
|
||||
|
||||
if (!hasSubpasses) {
|
||||
@@ -239,13 +235,13 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
.format = config.colorFormat[i],
|
||||
.samples = (VkSampleCountFlagBits) config.samples,
|
||||
.loadOp = clear ? kClear : (discard ? kDontCare : kKeep),
|
||||
.storeOp = config.samples == 1 ? kEnableStore : kDisableStore,
|
||||
.storeOp = kEnableStore,
|
||||
.stencilLoadOp = kDontCare,
|
||||
.stencilStoreOp = kDisableStore,
|
||||
.initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear)
|
||||
? ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT)
|
||||
: ImgUtil::getVkLayout(VulkanLayout::UNDEFINED),
|
||||
.finalLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
|
||||
? imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT)
|
||||
: imgutil::getVkLayout(VulkanLayout::UNDEFINED),
|
||||
.finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -272,7 +268,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
|
||||
pResolveAttachment->attachment = attachmentIndex;
|
||||
pResolveAttachment->layout
|
||||
= ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT_RESOLVE);
|
||||
= imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT_RESOLVE);
|
||||
++pResolveAttachment;
|
||||
|
||||
attachments[attachmentIndex++] = {
|
||||
@@ -282,8 +278,8 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
.storeOp = kEnableStore,
|
||||
.stencilLoadOp = kDontCare,
|
||||
.stencilStoreOp = kDisableStore,
|
||||
.initialLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
|
||||
.finalLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
|
||||
.initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT),
|
||||
.finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -292,7 +288,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
const bool clear = any(config.clear & TargetBufferFlags::DEPTH);
|
||||
const bool discardStart = any(config.discardStart & TargetBufferFlags::DEPTH);
|
||||
const bool discardEnd = any(config.discardEnd & TargetBufferFlags::DEPTH);
|
||||
depthAttachmentRef.layout = ImgUtil::getVkLayout(config.renderPassDepthLayout);
|
||||
depthAttachmentRef.layout = imgutil::getVkLayout(VulkanLayout::DEPTH_ATTACHMENT);
|
||||
depthAttachmentRef.attachment = attachmentIndex;
|
||||
attachments[attachmentIndex++] = {
|
||||
.format = config.depthFormat,
|
||||
@@ -301,8 +297,8 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
.storeOp = discardEnd ? kDisableStore : kEnableStore,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = ImgUtil::getVkLayout(config.initialDepthLayout),
|
||||
.finalLayout = ImgUtil::getVkLayout(config.finalDepthLayout),
|
||||
.initialLayout = imgutil::getVkLayout(config.initialDepthLayout),
|
||||
.finalLayout = imgutil::getVkLayout(FINAL_DEPTH_ATTACHMENT_LAYOUT),
|
||||
};
|
||||
}
|
||||
renderPassInfo.attachmentCount = attachmentIndex;
|
||||
|
||||
@@ -35,24 +35,25 @@ namespace filament::backend {
|
||||
//
|
||||
class VulkanFboCache {
|
||||
public:
|
||||
constexpr static VulkanLayout FINAL_COLOR_ATTACHMENT_LAYOUT = VulkanLayout::COLOR_ATTACHMENT;
|
||||
constexpr static VulkanLayout FINAL_RESOLVE_ATTACHMENT_LAYOUT = VulkanLayout::COLOR_ATTACHMENT;
|
||||
constexpr static VulkanLayout FINAL_DEPTH_ATTACHMENT_LAYOUT = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
|
||||
// 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.
|
||||
// TODO: This struct can be reduced in size by using a subset of formats instead of VkFormat
|
||||
// and removing the "finalDepthLayout" field.
|
||||
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 GENERAL.
|
||||
// - 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 : 4;
|
||||
VulkanLayout renderPassDepthLayout : 4;
|
||||
VulkanLayout finalDepthLayout : 4;
|
||||
uint8_t padding0 : 4;
|
||||
VulkanLayout initialDepthLayout : 8;
|
||||
uint8_t padding0;
|
||||
uint8_t padding1;
|
||||
|
||||
VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes
|
||||
@@ -63,7 +64,7 @@ public:
|
||||
uint8_t samples; // 1 byte
|
||||
uint8_t needsResolveMask; // 1 byte
|
||||
uint8_t subpassMask; // 1 byte
|
||||
bool padding2; // 1 byte
|
||||
uint8_t padding2; // 1 byte
|
||||
};
|
||||
struct RenderPassVal {
|
||||
VkRenderPass handle;
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanDriver.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "spirv/VulkanSpirvUtils.h"
|
||||
#include "utils/Log.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
@@ -30,15 +32,17 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
static void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) {
|
||||
namespace {
|
||||
|
||||
void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) {
|
||||
rect->offset.y = framebufferHeight - rect->offset.y - rect->extent.height;
|
||||
}
|
||||
|
||||
static void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
|
||||
void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
|
||||
rect->y = framebufferHeight - rect->y - rect->height;
|
||||
}
|
||||
|
||||
static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
|
||||
void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
|
||||
int32_t x = std::max(rect->offset.x, 0);
|
||||
int32_t y = std::max(rect->offset.y, 0);
|
||||
int32_t right = std::min(rect->offset.x + (int32_t) rect->extent.width, (int32_t) fbWidth);
|
||||
@@ -49,17 +53,165 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
|
||||
rect->extent.height = std::max(top - y, 0);
|
||||
}
|
||||
|
||||
template<typename Bitmask>
|
||||
static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) {
|
||||
Bitmask ret = 0;
|
||||
if (flags & ShaderStageFlags2::VERTEX) {
|
||||
ret |= (getVertexStage<Bitmask>() << binding);
|
||||
}
|
||||
if (flags & ShaderStageFlags2::FRAGMENT) {
|
||||
ret |= (getFragmentStage<Bitmask>() << binding);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr decltype(VulkanProgram::MAX_SHADER_MODULES) MAX_SHADER_MODULES =
|
||||
VulkanProgram::MAX_SHADER_MODULES;
|
||||
|
||||
using LayoutDescriptionList = VulkanProgram::LayoutDescriptionList;
|
||||
|
||||
template<typename Bitmask>
|
||||
void addDescriptors(Bitmask mask,
|
||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding>& outputList) {
|
||||
constexpr uint8_t MODULE_OFFSET = (sizeof(Bitmask) * 8) / MAX_SHADER_MODULES;
|
||||
for (uint8_t i = 0; i < MODULE_OFFSET; ++i) {
|
||||
bool const hasVertex = (mask & (1ULL << i)) != 0;
|
||||
bool const hasFragment = (mask & (1ULL << (MODULE_OFFSET + i))) != 0;
|
||||
if (!hasVertex && !hasFragment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DescriptorSetLayoutBinding binding{
|
||||
.binding = i,
|
||||
.flags = DescriptorFlags::NONE,
|
||||
.count = 0,// This is always 0 for now as we pass the size of the UBOs in the Driver API
|
||||
// instead.
|
||||
};
|
||||
if (hasVertex) {
|
||||
binding.stageFlags = ShaderStageFlags2::VERTEX;
|
||||
}
|
||||
if (hasFragment) {
|
||||
binding.stageFlags = static_cast<ShaderStageFlags2>(
|
||||
binding.stageFlags | ShaderStageFlags2::FRAGMENT);
|
||||
}
|
||||
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
|
||||
binding.type = DescriptorType::UNIFORM_BUFFER;
|
||||
} else if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
|
||||
binding.type = DescriptorType::SAMPLER;
|
||||
} else if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
|
||||
binding.type = DescriptorType::INPUT_ATTACHMENT;
|
||||
}
|
||||
outputList.push_back(binding);
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
case backend::ShaderStage::FRAGMENT:
|
||||
return VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
case backend::ShaderStage::COMPUTE:
|
||||
PANIC_POSTCONDITION("Unsupported stage");
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device,
|
||||
VkDescriptorSetLayoutCreateInfo const& info, Bitmask const& bitmask)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
|
||||
mDevice(device),
|
||||
vklayout(createDescriptorSetLayout(device, info)),
|
||||
bitmask(bitmask),
|
||||
bindings(getBindings(bitmask)),
|
||||
count(Count::fromLayoutBitmask(bitmask)) {}
|
||||
|
||||
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
|
||||
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
|
||||
}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
|
||||
mRangeCount = 0;
|
||||
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
|
||||
auto const& constants = program.getPushConstants(stage);
|
||||
if (constants.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We store the type of the constant for type-checking when writing.
|
||||
auto& types = mTypes[(uint8_t) stage];
|
||||
types.reserve(constants.size());
|
||||
std::for_each(constants.cbegin(), constants.cend(), [&types] (Program::PushConstant t) {
|
||||
types.push_back(t.type);
|
||||
});
|
||||
|
||||
mRanges[mRangeCount++] = {
|
||||
.stageFlags = getVkStage(stage),
|
||||
.offset = 0,
|
||||
.size = (uint32_t) constants.size() * ENTRY_SIZE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
|
||||
VulkanCommandBuffer* cmdbuf = &(commands->get());
|
||||
uint32_t binaryValue = 0;
|
||||
UTILS_UNUSED_IN_RELEASE auto const& types = mTypes[(uint8_t) stage];
|
||||
if (std::holds_alternative<bool>(value)) {
|
||||
assert_invariant(types[index] == ConstantType::BOOL);
|
||||
bool const bval = std::get<bool>(value);
|
||||
binaryValue = static_cast<uint32_t const>(bval ? VK_TRUE : VK_FALSE);
|
||||
} else if (std::holds_alternative<float>(value)) {
|
||||
assert_invariant(types[index] == ConstantType::FLOAT);
|
||||
float const fval = std::get<float>(value);
|
||||
binaryValue = *reinterpret_cast<uint32_t const*>(&fval);
|
||||
} else {
|
||||
assert_invariant(types[index] == ConstantType::INT);
|
||||
int const ival = std::get<int>(value);
|
||||
binaryValue = *reinterpret_cast<uint32_t const*>(&ival);
|
||||
}
|
||||
vkCmdPushConstants(cmdbuf->buffer(), layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE,
|
||||
&binaryValue);
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
: HwProgram(builder.getName()),
|
||||
VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo()),
|
||||
mInfo(new(std::nothrow) PipelineInfo(builder)),
|
||||
mDevice(device) {
|
||||
|
||||
constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES;
|
||||
constexpr uint8_t SAMPLER_MODULE_OFFSET = (sizeof(SamplerBitmask) * 8) / MAX_SHADER_MODULES;
|
||||
constexpr uint8_t INPUT_ATTACHMENT_MODULE_OFFSET =
|
||||
(sizeof(InputAttachmentBitmask) * 8) / MAX_SHADER_MODULES;
|
||||
|
||||
Program::ShaderSource const& blobs = builder.getShadersSource();
|
||||
auto& modules = mInfo->shaders;
|
||||
|
||||
auto const& specializationConstants = builder.getSpecializationConstants();
|
||||
|
||||
std::vector<uint32_t> shader;
|
||||
|
||||
// TODO: this will be moved out of the shader as the descriptor set layout will be provided by
|
||||
// Filament instead of parsed from the shaders. See [GDSR] in VulkanDescriptorSetManager.h
|
||||
UniformBufferBitmask uboMask = 0;
|
||||
SamplerBitmask samplerMask = 0;
|
||||
InputAttachmentBitmask inputAttachmentMask = 0;
|
||||
|
||||
static_assert(static_cast<ShaderStage>(0) == ShaderStage::VERTEX &&
|
||||
static_cast<ShaderStage>(1) == ShaderStage::FRAGMENT &&
|
||||
MAX_SHADER_MODULES == 2);
|
||||
|
||||
for (size_t i = 0; i < MAX_SHADER_MODULES; i++) {
|
||||
Program::ShaderBlob const& blob = blobs[i];
|
||||
|
||||
@@ -72,6 +224,12 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
dataSize = shader.size() * 4;
|
||||
}
|
||||
|
||||
auto const [ubo, sampler, inputAttachment] = getProgramBindings(blob);
|
||||
uboMask |= (static_cast<UniformBufferBitmask>(ubo) << (UBO_MODULE_OFFSET * i));
|
||||
samplerMask |= (static_cast<SamplerBitmask>(sampler) << (SAMPLER_MODULE_OFFSET * i));
|
||||
inputAttachmentMask |= (static_cast<InputAttachmentBitmask>(inputAttachment)
|
||||
<< (INPUT_ATTACHMENT_MODULE_OFFSET * i));
|
||||
|
||||
VkShaderModule& module = modules[i];
|
||||
VkShaderModuleCreateInfo moduleInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
@@ -99,20 +257,33 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
LayoutDescriptionList& layouts = mInfo->layouts;
|
||||
layouts[0].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
|
||||
countBits(collapseStages(uboMask)));
|
||||
layouts[1].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
|
||||
countBits(collapseStages(samplerMask)));
|
||||
layouts[2].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
|
||||
countBits(collapseStages(inputAttachmentMask)));
|
||||
|
||||
addDescriptors(uboMask, layouts[0].bindings);
|
||||
addDescriptors(samplerMask, layouts[1].bindings);
|
||||
addDescriptors(inputAttachmentMask, layouts[2].bindings);
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
auto& bindingToName = mInfo->bindingToName;
|
||||
#endif
|
||||
|
||||
auto& groupInfo = builder.getSamplerGroupInfo();
|
||||
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
|
||||
auto& usage = mInfo->usage;
|
||||
auto& bindings = mInfo->bindings;
|
||||
for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) {
|
||||
auto const& group = groupInfo[groupInd];
|
||||
auto const& samplers = group.samplers;
|
||||
for (size_t i = 0; i < samplers.size(); ++i) {
|
||||
uint32_t const binding = samplers[i].binding;
|
||||
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
|
||||
usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage);
|
||||
assert_invariant(bindings.find(binding) == bindings.end());
|
||||
bindings.insert(binding);
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
bindingToName[binding] = samplers[i].name.c_str();
|
||||
@@ -126,24 +297,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
|
||||
CustomSamplerInfoList const& samplerInfo) noexcept
|
||||
: VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo()),
|
||||
mDevice(device) {
|
||||
mInfo->shaders[0] = vs;
|
||||
mInfo->shaders[1] = fs;
|
||||
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
|
||||
auto& usage = mInfo->usage;
|
||||
bindingToSamplerIndex.resize(samplerInfo.size());
|
||||
for (uint16_t binding = 0; binding < samplerInfo.size(); ++binding) {
|
||||
auto const& sampler = samplerInfo[binding];
|
||||
bindingToSamplerIndex[binding]
|
||||
= (sampler.groupIndex << 8) | (0xff & sampler.samplerIndex);
|
||||
usage = VulkanPipelineCache::getUsageFlags(binding, sampler.flags, usage);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanProgram::~VulkanProgram() {
|
||||
for (auto shader: mInfo->shaders) {
|
||||
vkDestroyShaderModule(mDevice, shader, VKALLOC);
|
||||
@@ -187,13 +340,13 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
||||
|
||||
// Constrain the sample count according to both kinds of sample count masks obtained from
|
||||
// VkPhysicalDeviceProperties. This is consistent with the VulkanTexture constructor.
|
||||
const auto& limits = context.getPhysicalDeviceLimits();
|
||||
auto const& limits = context.getPhysicalDeviceLimits();
|
||||
mSamples = samples = reduceSampleCount(samples, limits.framebufferDepthSampleCounts &
|
||||
limits.framebufferColorSampleCounts);
|
||||
|
||||
// Create sidecar MSAA textures for color attachments if they don't already exist.
|
||||
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||
const VulkanAttachment& spec = color[index];
|
||||
VulkanAttachment const& spec = color[index];
|
||||
VulkanTexture* texture = (VulkanTexture*) spec.texture;
|
||||
if (texture && texture->samples == 1) {
|
||||
auto msTexture = texture->getSidecar();
|
||||
@@ -256,19 +409,19 @@ VkExtent2D VulkanRenderTarget::getExtent() const {
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
VulkanAttachment VulkanRenderTarget::getColor(int target) const {
|
||||
VulkanAttachment& VulkanRenderTarget::getColor(int target) {
|
||||
return mColor[target];
|
||||
}
|
||||
|
||||
VulkanAttachment VulkanRenderTarget::getMsaaColor(int target) const {
|
||||
VulkanAttachment& VulkanRenderTarget::getMsaaColor(int target) {
|
||||
return mMsaaAttachments[target];
|
||||
}
|
||||
|
||||
VulkanAttachment VulkanRenderTarget::getDepth() const {
|
||||
VulkanAttachment& VulkanRenderTarget::getDepth() {
|
||||
return mDepth;
|
||||
}
|
||||
|
||||
VulkanAttachment VulkanRenderTarget::getMsaaDepth() const {
|
||||
VulkanAttachment& VulkanRenderTarget::getMsaaDepth() {
|
||||
return mMsaaDepthAttachment;
|
||||
}
|
||||
|
||||
@@ -405,4 +558,35 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAl
|
||||
mResources.acquire(indexBuffer);
|
||||
}
|
||||
|
||||
using Bitmask = VulkanDescriptorSetLayout::Bitmask;
|
||||
|
||||
Bitmask Bitmask::fromBackendLayout(descset::DescriptorSetLayout const& layout) {
|
||||
Bitmask mask;
|
||||
for (auto const& binding: layout.bindings) {
|
||||
switch (binding.type) {
|
||||
case descset::DescriptorType::UNIFORM_BUFFER: {
|
||||
if (binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET) {
|
||||
mask.dynamicUbo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
|
||||
binding.binding);
|
||||
} else {
|
||||
mask.ubo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
|
||||
binding.binding);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case descset::DescriptorType::SAMPLER: {
|
||||
mask.sampler |= fromStageFlags<SamplerBitmask>(binding.stageFlags, binding.binding);
|
||||
break;
|
||||
}
|
||||
case descset::DescriptorType::INPUT_ATTACHMENT: {
|
||||
mask.inputAttachment |=
|
||||
fromStageFlags<InputAttachmentBitmask>(binding.stageFlags, binding.binding);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -21,35 +21,193 @@
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanSwapChain.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
#include <private/backend/SamplerGroup.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/StructureOfArrays.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace descset;
|
||||
|
||||
class VulkanTimestamps;
|
||||
|
||||
struct VulkanDescriptorSetLayout : public VulkanResource {
|
||||
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3;
|
||||
|
||||
// The bitmask representation of a set layout.
|
||||
struct Bitmask {
|
||||
UniformBufferBitmask ubo = 0; // 4 bytes
|
||||
UniformBufferBitmask dynamicUbo = 0; // 4 bytes
|
||||
SamplerBitmask sampler = 0; // 8 bytes
|
||||
InputAttachmentBitmask inputAttachment = 0; // 1 bytes
|
||||
|
||||
// Because we're using this struct as hash key, must make it's 8-bytes aligned, with no
|
||||
// unaccounted bytes.
|
||||
uint8_t padding0 = 0; // 1 bytes
|
||||
uint16_t padding1 = 0;// 2 bytes
|
||||
uint32_t padding2 = 0;// 4 bytes
|
||||
|
||||
bool operator==(Bitmask const& right) const {
|
||||
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
|
||||
inputAttachment == right.inputAttachment;
|
||||
}
|
||||
|
||||
static Bitmask fromBackendLayout(descset::DescriptorSetLayout const& layout);
|
||||
};
|
||||
|
||||
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
|
||||
// pools.
|
||||
struct Count {
|
||||
uint32_t ubo = 0;
|
||||
uint32_t dynamicUbo = 0;
|
||||
uint32_t sampler = 0;
|
||||
uint32_t inputAttachment = 0;
|
||||
|
||||
bool operator==(Count const& right) const noexcept {
|
||||
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
|
||||
inputAttachment == right.inputAttachment;
|
||||
}
|
||||
|
||||
static inline Count fromLayoutBitmask(Bitmask const& mask) {
|
||||
return {
|
||||
.ubo = countBits(collapseStages(mask.ubo)),
|
||||
.dynamicUbo = countBits(collapseStages(mask.dynamicUbo)),
|
||||
.sampler = countBits(collapseStages(mask.sampler)),
|
||||
.inputAttachment = countBits(collapseStages(mask.inputAttachment)),
|
||||
};
|
||||
}
|
||||
|
||||
Count operator*(uint16_t mult) const noexcept {
|
||||
// TODO: check for overflow.
|
||||
|
||||
Count ret;
|
||||
ret.ubo = ubo * mult;
|
||||
ret.dynamicUbo = dynamicUbo * mult;
|
||||
ret.sampler = sampler * mult;
|
||||
ret.inputAttachment = inputAttachment * mult;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Bitmask) % 8 == 0);
|
||||
|
||||
explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
|
||||
Bitmask const& bitmask);
|
||||
|
||||
~VulkanDescriptorSetLayout();
|
||||
|
||||
VkDevice const mDevice;
|
||||
VkDescriptorSetLayout const vklayout;
|
||||
Bitmask const bitmask;
|
||||
|
||||
// This is a convenience struct so that we don't have to iterate through all the bits of the
|
||||
// bitmask (which correspondings to binding indices).
|
||||
struct _Bindings {
|
||||
utils::FixedCapacityVector<uint8_t> const ubo;
|
||||
utils::FixedCapacityVector<uint8_t> const dynamicUbo;
|
||||
utils::FixedCapacityVector<uint8_t> const sampler;
|
||||
utils::FixedCapacityVector<uint8_t> const inputAttachment;
|
||||
} bindings;
|
||||
|
||||
Count const count;
|
||||
|
||||
private:
|
||||
|
||||
template <typename MaskType>
|
||||
utils::FixedCapacityVector<uint8_t> bits(MaskType mask) {
|
||||
utils::FixedCapacityVector<uint8_t> ret =
|
||||
utils::FixedCapacityVector<uint8_t>::with_capacity(countBits(mask));
|
||||
for (uint8_t i = 0; i < sizeof(mask) * 4; ++i) {
|
||||
if (mask & (1 << i)) {
|
||||
ret.push_back(i);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
_Bindings getBindings(Bitmask const& bitmask) {
|
||||
auto const uboCollapsed = collapseStages(bitmask.ubo);
|
||||
auto const dynamicUboCollapsed = collapseStages(bitmask.dynamicUbo);
|
||||
auto const samplerCollapsed = collapseStages(bitmask.sampler);
|
||||
auto const inputAttachmentCollapsed = collapseStages(bitmask.inputAttachment);
|
||||
return {
|
||||
bits(uboCollapsed),
|
||||
bits(dynamicUboCollapsed),
|
||||
bits(samplerCollapsed),
|
||||
bits(inputAttachmentCollapsed),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using VulkanDescriptorSetLayoutList = std::array<Handle<VulkanDescriptorSetLayout>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
struct VulkanDescriptorSet : public VulkanResource {
|
||||
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(VulkanResourceAllocator* allocator,
|
||||
VkDescriptorSet rawSet, OnRecycle&& onRecycleFn)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
|
||||
resources(allocator),
|
||||
vkSet(rawSet),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
|
||||
~VulkanDescriptorSet() {
|
||||
if (mOnRecycleFn) {
|
||||
mOnRecycleFn();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe change to fixed size for performance.
|
||||
VulkanAcquireOnlyResourceManager resources;
|
||||
VkDescriptorSet const vkSet;
|
||||
|
||||
private:
|
||||
OnRecycle mOnRecycleFn;
|
||||
};
|
||||
|
||||
using VulkanDescriptorSetList = std::array<Handle<VulkanDescriptorSet>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
|
||||
using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>;
|
||||
|
||||
struct PushConstantDescription {
|
||||
|
||||
explicit PushConstantDescription(backend::Program const& program) noexcept;
|
||||
|
||||
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
|
||||
|
||||
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
|
||||
|
||||
void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage,
|
||||
uint8_t index, backend::PushConstantVariant const& value);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t);
|
||||
|
||||
utils::FixedCapacityVector<backend::ConstantType> mTypes[Program::SHADER_TYPE_COUNT];
|
||||
VkPushConstantRange mRanges[Program::SHADER_TYPE_COUNT];
|
||||
uint32_t mRangeCount;
|
||||
};
|
||||
|
||||
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
using BindingList = CappedArray<uint16_t, MAX_SAMPLER_COUNT>;
|
||||
|
||||
VulkanProgram(VkDevice device, Program const& builder) noexcept;
|
||||
|
||||
struct CustomSamplerInfo {
|
||||
uint8_t groupIndex;
|
||||
uint8_t samplerIndex;
|
||||
ShaderStageFlags flags;
|
||||
};
|
||||
using CustomSamplerInfoList = utils::FixedCapacityVector<CustomSamplerInfo>;
|
||||
|
||||
// We allow custom descriptor of the samplers within shaders. This is needed if we want to use
|
||||
// a program that exists only in the backend - for example, for shader-based bliting.
|
||||
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
|
||||
CustomSamplerInfoList const& samplerInfo) noexcept;
|
||||
~VulkanProgram();
|
||||
|
||||
inline VkShaderModule getVertexShader() const {
|
||||
@@ -58,35 +216,69 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
|
||||
|
||||
inline VulkanPipelineCache::UsageFlags getUsage() const { return mInfo->usage; }
|
||||
|
||||
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; }
|
||||
|
||||
// TODO: this is currently not used. This will replace getLayoutDescriptionList below.
|
||||
// inline descset::DescriptorSetLayout const& getLayoutDescription() const {
|
||||
// return mInfo->layout;
|
||||
// }
|
||||
// In the usual case, we would have just one layout per program. But in the current setup, we
|
||||
// have a set/layout for each descriptor type. This will be changed in the future.
|
||||
using LayoutDescriptionList = std::array<descset::DescriptorSetLayout,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; }
|
||||
|
||||
inline uint32_t getPushConstantRangeCount() const {
|
||||
return mInfo->pushConstantDescription.getVkRangeCount();
|
||||
}
|
||||
|
||||
inline VkPushConstantRange const* getPushConstantRanges() const {
|
||||
return mInfo->pushConstantDescription.getVkRanges();
|
||||
}
|
||||
|
||||
inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
|
||||
mInfo->pushConstantDescription.write(commands, layout, stage, index, value);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
inline utils::FixedCapacityVector<std::string> const& getBindingToName() const {
|
||||
return mInfo->bindingToName;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
// TODO: handle compute shaders.
|
||||
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
|
||||
static constexpr uint8_t const MAX_SHADER_MODULES = 2;
|
||||
|
||||
private:
|
||||
struct PipelineInfo {
|
||||
PipelineInfo()
|
||||
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) {}
|
||||
explicit PipelineInfo(backend::Program const& program) noexcept
|
||||
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
|
||||
pushConstantDescription(program)
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
, bindingToName(MAX_SAMPLER_COUNT, "")
|
||||
#endif
|
||||
{}
|
||||
|
||||
// This bitset maps to each of the sampler in the sampler groups associated with this
|
||||
// program, and whether each sampler is used in which shader (i.e. vert, frag, compute).
|
||||
VulkanPipelineCache::UsageFlags usage;
|
||||
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 };
|
||||
|
||||
// TODO: Use this instead of `layouts` after Filament-side Descriptor Set API is in place.
|
||||
// descset::DescriptorSetLayout layout;
|
||||
LayoutDescriptionList layouts;
|
||||
|
||||
PushConstantDescription pushConstantDescription;
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
// We store the sampler name mapped from binding index (only for debug purposes).
|
||||
utils::FixedCapacityVector<std::string> bindingToName;
|
||||
@@ -120,10 +312,11 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
void transformClientRectToPlatform(VkRect2D* bounds) const;
|
||||
void transformClientRectToPlatform(VkViewport* bounds) const;
|
||||
VkExtent2D getExtent() const;
|
||||
VulkanAttachment getColor(int target) const;
|
||||
VulkanAttachment getMsaaColor(int target) const;
|
||||
VulkanAttachment getDepth() const;
|
||||
VulkanAttachment getMsaaDepth() const;
|
||||
// We return references in the following methods to avoid a copy.
|
||||
VulkanAttachment& getColor(int target);
|
||||
VulkanAttachment& getMsaaColor(int target);
|
||||
VulkanAttachment& getDepth();
|
||||
VulkanAttachment& getMsaaDepth();
|
||||
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
|
||||
uint8_t getSamples() const { return mSamples; }
|
||||
bool hasDepth() const { return mDepth.texture; }
|
||||
|
||||
@@ -26,38 +26,10 @@
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
namespace filament::backend::imgutil {
|
||||
|
||||
namespace {
|
||||
|
||||
inline VkImageLayout getVkImageLayout(VulkanLayout layout) {
|
||||
switch (layout) {
|
||||
case VulkanLayout::UNDEFINED:
|
||||
return VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
case VulkanLayout::READ_WRITE:
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
case VulkanLayout::READ_ONLY:
|
||||
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
case VulkanLayout::TRANSFER_SRC:
|
||||
return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
case VulkanLayout::TRANSFER_DST:
|
||||
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
case VulkanLayout::DEPTH_ATTACHMENT:
|
||||
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
case VulkanLayout::DEPTH_SAMPLER:
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
case VulkanLayout::PRESENT:
|
||||
return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
// Filament sometimes samples from one miplevel while writing to another level in the same
|
||||
// texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive layout
|
||||
// transitions. So, keep it simple and use GENERAL for all color-attachable textures.
|
||||
case VulkanLayout::COLOR_ATTACHMENT:
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
case VulkanLayout::COLOR_ATTACHMENT_RESOLVE:
|
||||
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::tuple<VkAccessFlags, VkAccessFlags, VkPipelineStageFlags, VkPipelineStageFlags,
|
||||
VkImageLayout, VkImageLayout>
|
||||
getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
@@ -70,11 +42,11 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
srcStage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
|
||||
break;
|
||||
case VulkanLayout::COLOR_ATTACHMENT:
|
||||
srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT
|
||||
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
|
||||
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
||||
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
break;
|
||||
case VulkanLayout::READ_WRITE:
|
||||
srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
@@ -154,27 +126,12 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
}
|
||||
|
||||
return std::make_tuple(srcAccessMask, dstAccessMask, srcStage, dstStage,
|
||||
getVkImageLayout(transition.oldLayout), getVkImageLayout(transition.newLayout));
|
||||
getVkLayout(transition.oldLayout), getVkLayout(transition.newLayout));
|
||||
}
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
VkImageViewType VulkanImageUtility::getViewType(SamplerType target) {
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
return VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
return VK_IMAGE_VIEW_TYPE_3D;
|
||||
default:
|
||||
return VK_IMAGE_VIEW_TYPE_2D;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanImageUtility::transitionLayout(VkCommandBuffer cmdbuffer,
|
||||
void transitionLayout(VkCommandBuffer cmdbuffer,
|
||||
VulkanLayoutTransition transition) {
|
||||
if (transition.oldLayout == transition.newLayout) {
|
||||
return;
|
||||
@@ -197,10 +154,6 @@ void VulkanImageUtility::transitionLayout(VkCommandBuffer cmdbuffer,
|
||||
vkCmdPipelineBarrier(cmdbuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
}
|
||||
|
||||
VkImageLayout VulkanImageUtility::getVkLayout(VulkanLayout layout) {
|
||||
return getVkImageLayout(layout);
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
|
||||
@@ -217,12 +170,12 @@ bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange&
|
||||
return false;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION | FVK_DEBUG_TEXTURE)
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#define CASE(VALUE) \
|
||||
case filament::backend::VulkanLayout::VALUE: { \
|
||||
out << #VALUE; \
|
||||
out << " [" \
|
||||
<< filament::backend::VulkanImageUtility::getVkLayout( \
|
||||
<< filament::backend::imgutil::getVkLayout( \
|
||||
filament::backend::VulkanLayout::VALUE) \
|
||||
<< "]"; \
|
||||
break; \
|
||||
|
||||
@@ -59,44 +59,85 @@ struct VulkanLayoutTransition {
|
||||
VkImageSubresourceRange subresources;
|
||||
};
|
||||
|
||||
class VulkanImageUtility {
|
||||
public:
|
||||
static VkImageViewType getViewType(SamplerType target);
|
||||
namespace imgutil {
|
||||
|
||||
inline static VulkanLayout getDefaultLayout(TextureUsage usage) {
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
return VulkanLayout::DEPTH_SAMPLER;
|
||||
} else {
|
||||
return VulkanLayout::DEPTH_ATTACHMENT;
|
||||
}
|
||||
}
|
||||
inline VkImageViewType getViewType(SamplerType target) {
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
return VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
return VK_IMAGE_VIEW_TYPE_3D;
|
||||
default:
|
||||
return VK_IMAGE_VIEW_TYPE_2D;
|
||||
}
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
return VulkanLayout::COLOR_ATTACHMENT;
|
||||
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;
|
||||
}
|
||||
// Finally, the layout for an immutable texture is optimal read-only.
|
||||
return VulkanLayout::READ_ONLY;
|
||||
}
|
||||
|
||||
inline static 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);
|
||||
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;
|
||||
}
|
||||
|
||||
static VkImageLayout getVkLayout(VulkanLayout layout);
|
||||
|
||||
static void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
|
||||
};
|
||||
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:
|
||||
return VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
case VulkanLayout::READ_WRITE:
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
case VulkanLayout::READ_ONLY:
|
||||
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
case VulkanLayout::TRANSFER_SRC:
|
||||
return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
case VulkanLayout::TRANSFER_DST:
|
||||
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
case VulkanLayout::DEPTH_ATTACHMENT:
|
||||
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
case VulkanLayout::DEPTH_SAMPLER:
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
case VulkanLayout::PRESENT:
|
||||
return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
// Filament sometimes samples from one miplevel while writing to another level in the
|
||||
// same texture (e.g. bloom does this). Moreover we'd like to avoid lots of expensive
|
||||
// layout transitions. So, keep it simple and use GENERAL for all color-attachable
|
||||
// textures.
|
||||
case VulkanLayout::COLOR_ATTACHMENT:
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
case VulkanLayout::COLOR_ATTACHMENT_RESOLVE:
|
||||
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
}
|
||||
|
||||
void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
|
||||
|
||||
} // namespace imgutil
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
|
||||
#include "vk_mem_alloc.h"
|
||||
|
||||
VK_DEFINE_HANDLE(VmaAllocator)
|
||||
VK_DEFINE_HANDLE(VmaAllocation)
|
||||
VK_DEFINE_HANDLE(VmaPool)
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANMEMORY_H
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "vulkan/VulkanMemory.h"
|
||||
#include "vulkan/VulkanPipelineCache.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "caching/VulkanDescriptorSetManager.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -34,63 +35,9 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
static VkShaderStageFlags getShaderStageFlags(VulkanPipelineCache::UsageFlags key, uint16_t binding) {
|
||||
// NOTE: if you modify this function, you also need to modify getUsageFlags.
|
||||
assert_invariant(binding < MAX_SAMPLER_COUNT);
|
||||
VkShaderStageFlags flags = 0;
|
||||
if (key.test(binding)) {
|
||||
flags |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
}
|
||||
if (key.test(MAX_SAMPLER_COUNT + binding)) {
|
||||
flags |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
VulkanPipelineCache::UsageFlags
|
||||
VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) {
|
||||
// NOTE: if you modify this function, you also need to modify getShaderStageFlags.
|
||||
assert_invariant(binding < MAX_SAMPLER_COUNT);
|
||||
if (any(flags & ShaderStageFlags::VERTEX)) {
|
||||
src.set(binding);
|
||||
}
|
||||
if (any(flags & ShaderStageFlags::FRAGMENT)) {
|
||||
src.set(MAX_SAMPLER_COUNT + binding);
|
||||
}
|
||||
// TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags
|
||||
// has 186 bits (MAX_SAMPLER_COUNT * 3)
|
||||
// assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)));
|
||||
return src;
|
||||
}
|
||||
|
||||
VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding,
|
||||
UsageFlags src) {
|
||||
src.unset(binding);
|
||||
src.unset(MAX_SAMPLER_COUNT + binding);
|
||||
return src;
|
||||
}
|
||||
|
||||
VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator)
|
||||
: mResourceAllocator(allocator),
|
||||
mPipelineBoundResources(allocator) {
|
||||
mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
mDummyBufferWriteInfo.pNext = nullptr;
|
||||
mDummyBufferWriteInfo.dstArrayElement = 0;
|
||||
mDummyBufferWriteInfo.descriptorCount = 1;
|
||||
mDummyBufferWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
mDummyBufferWriteInfo.pImageInfo = nullptr;
|
||||
mDummyBufferWriteInfo.pBufferInfo = &mDummyBufferInfo;
|
||||
mDummyBufferWriteInfo.pTexelBufferView = nullptr;
|
||||
|
||||
mDummyTargetInfo.imageLayout = VulkanImageUtility::getVkLayout(VulkanLayout::READ_ONLY);
|
||||
mDummyTargetWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
mDummyTargetWriteInfo.pNext = nullptr;
|
||||
mDummyTargetWriteInfo.dstArrayElement = 0;
|
||||
mDummyTargetWriteInfo.descriptorCount = 1;
|
||||
mDummyTargetWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
||||
mDummyTargetWriteInfo.pImageInfo = &mDummyTargetInfo;
|
||||
mDummyTargetWriteInfo.pBufferInfo = nullptr;
|
||||
mDummyTargetWriteInfo.pTexelBufferView = nullptr;
|
||||
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VmaAllocator allocator)
|
||||
: mDevice(device),
|
||||
mAllocator(allocator) {
|
||||
}
|
||||
|
||||
VulkanPipelineCache::~VulkanPipelineCache() {
|
||||
@@ -98,244 +45,43 @@ VulkanPipelineCache::~VulkanPipelineCache() {
|
||||
// be explicit about teardown order of various components.
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::setDevice(VkDevice device, VmaAllocator allocator) {
|
||||
assert_invariant(mDevice == VK_NULL_HANDLE);
|
||||
mDevice = device;
|
||||
mAllocator = allocator;
|
||||
mDescriptorPool = createDescriptorPool(mDescriptorPoolSize);
|
||||
|
||||
// Formulate some dummy objects and dummy descriptor info used only for clearing out unused
|
||||
// bindings. This is especially crucial after a texture has been destroyed. Since core Vulkan
|
||||
// does not allow specifying VK_NULL_HANDLE without the robustness2 extension, we would need to
|
||||
// change the pipeline layout more frequently if we wanted to get rid of these dummy objects.
|
||||
|
||||
VkBufferCreateInfo bufferInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = 16,
|
||||
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
};
|
||||
VmaAllocationCreateInfo allocInfo { .usage = VMA_MEMORY_USAGE_GPU_ONLY };
|
||||
vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo, &mDummyBuffer, &mDummyMemory, nullptr);
|
||||
|
||||
mDummyBufferInfo.buffer = mDummyBuffer;
|
||||
mDummyBufferInfo.range = bufferInfo.size;
|
||||
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
|
||||
mPipelineRequirements.layout = layout;
|
||||
}
|
||||
|
||||
bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
|
||||
DescriptorMap::iterator descriptorIter = mDescriptorSets.find(mDescriptorRequirements);
|
||||
|
||||
// Check if the required descriptors are already bound. If so, there's no need to do anything.
|
||||
if (DescEqual equals; UTILS_LIKELY(equals(mBoundDescriptor, mDescriptorRequirements))) {
|
||||
|
||||
// If the pipeline state during an app's first draw call happens to match the default state
|
||||
// vector of the cache, then the cache is uninitialized and we should not return early.
|
||||
if (UTILS_LIKELY(!mDescriptorSets.empty())) {
|
||||
|
||||
// Since the descriptors are already bound, they should be found in the cache.
|
||||
assert_invariant(descriptorIter != mDescriptorSets.end());
|
||||
|
||||
// Update the LRU "time stamp" (really a count of cmd buf submissions) before returning.
|
||||
descriptorIter.value().lastUsed = mCurrentTime;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipeline() noexcept {
|
||||
// If a cached object exists, re-use it, otherwise create a new one.
|
||||
DescriptorCacheEntry* cacheEntry = UTILS_LIKELY(descriptorIter != mDescriptorSets.end()) ?
|
||||
&descriptorIter.value() : createDescriptorSets();
|
||||
|
||||
// If a descriptor set overflow occurred, allow higher levels to handle it gracefully.
|
||||
assert_invariant(cacheEntry != nullptr);
|
||||
if (UTILS_UNLIKELY(cacheEntry == nullptr)) {
|
||||
return false;
|
||||
if (PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
|
||||
pipelineIter != mPipelines.end()) {
|
||||
auto& pipeline = pipelineIter.value();
|
||||
pipeline.lastUsed = mCurrentTime;
|
||||
return &pipeline;
|
||||
}
|
||||
|
||||
cacheEntry->lastUsed = mCurrentTime;
|
||||
mBoundDescriptor = mDescriptorRequirements;
|
||||
// This passes the currently "bound" uniform buffer objects to pipeline that will be used in the
|
||||
// draw call.
|
||||
auto resourceEntry = mDescriptorResources.find(cacheEntry->id);
|
||||
if (resourceEntry == mDescriptorResources.end()) {
|
||||
mDescriptorResources[cacheEntry->id]
|
||||
= std::make_unique<VulkanAcquireOnlyResourceManager>(mResourceAllocator);
|
||||
resourceEntry = mDescriptorResources.find(cacheEntry->id);
|
||||
}
|
||||
resourceEntry->second->acquireAll(&mPipelineBoundResources);
|
||||
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT,
|
||||
cacheEntry->handles.data(), 0, nullptr);
|
||||
|
||||
return true;
|
||||
auto ret = createPipeline();
|
||||
ret->lastUsed = mCurrentTime;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) noexcept {
|
||||
void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
|
||||
PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
|
||||
|
||||
PipelineCacheEntry* cacheEntry = getOrCreatePipeline();
|
||||
// Check if the required pipeline is already bound.
|
||||
if (PipelineEqual equals; UTILS_LIKELY(equals(mBoundPipeline, mPipelineRequirements))) {
|
||||
assert_invariant(pipelineIter != mPipelines.end());
|
||||
pipelineIter.value().lastUsed = mCurrentTime;
|
||||
return true;
|
||||
if (cacheEntry->handle == commands->pipeline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a cached object exists, re-use it, otherwise create a new one.
|
||||
PipelineCacheEntry* cacheEntry = UTILS_LIKELY(pipelineIter != mPipelines.end()) ?
|
||||
&pipelineIter.value() : createPipeline();
|
||||
|
||||
// If an error occurred, allow higher levels to handle it gracefully.
|
||||
assert_invariant(cacheEntry != nullptr);
|
||||
if (UTILS_UNLIKELY(cacheEntry == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cacheEntry->lastUsed = mCurrentTime;
|
||||
getOrCreatePipelineLayout()->lastUsed = mCurrentTime;
|
||||
assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline");
|
||||
|
||||
mBoundPipeline = mPipelineRequirements;
|
||||
|
||||
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept {
|
||||
if (UTILS_UNLIKELY(!equivalent(mCurrentScissor, scissor))) {
|
||||
mCurrentScissor = scissor;
|
||||
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptorSets() noexcept {
|
||||
PipelineLayoutCacheEntry* layoutCacheEntry = getOrCreatePipelineLayout();
|
||||
|
||||
DescriptorCacheEntry descriptorCacheEntry = {
|
||||
.pipelineLayout = mPipelineRequirements.layout,
|
||||
.id = mDescriptorCacheEntryCount++,
|
||||
};
|
||||
|
||||
// Each of the arenas for this particular layout are guaranteed to have the same size. Check
|
||||
// the first arena to see if any descriptor sets are available that can be re-claimed. If not,
|
||||
// create brand new ones (one for each type). They will be added to the arena later, after they
|
||||
// are no longer used. This occurs during the cleanup phase during command buffer submission.
|
||||
auto& descriptorSetArenas = layoutCacheEntry->descriptorSetArenas;
|
||||
if (descriptorSetArenas[0].empty()) {
|
||||
|
||||
// If allocating a new descriptor set from the pool would cause it to overflow, then
|
||||
// recreate the pool. The number of descriptor sets that have already been allocated from
|
||||
// the pool is the sum of the "active" descriptor sets (mDescriptorSets) and the "dormant"
|
||||
// descriptor sets (mDescriptorArenasCount).
|
||||
//
|
||||
// NOTE: technically both sides of the inequality below should be multiplied by
|
||||
// DESCRIPTOR_TYPE_COUNT to get the true number of descriptor sets.
|
||||
if (mDescriptorSets.size() + mDescriptorArenasCount + 1 > mDescriptorPoolSize) {
|
||||
growDescriptorPool();
|
||||
}
|
||||
|
||||
VkDescriptorSetAllocateInfo allocInfo = {};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
allocInfo.descriptorPool = mDescriptorPool;
|
||||
allocInfo.descriptorSetCount = DESCRIPTOR_TYPE_COUNT;
|
||||
allocInfo.pSetLayouts = layoutCacheEntry->descriptorSetLayouts.data();
|
||||
VkResult error = vkAllocateDescriptorSets(mDevice, &allocInfo,
|
||||
descriptorCacheEntry.handles.data());
|
||||
assert_invariant(error == VK_SUCCESS);
|
||||
if (error != VK_SUCCESS) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) {
|
||||
descriptorCacheEntry.handles[i] = descriptorSetArenas[i].back();
|
||||
descriptorSetArenas[i].pop_back();
|
||||
}
|
||||
assert_invariant(mDescriptorArenasCount > 0);
|
||||
mDescriptorArenasCount--;
|
||||
}
|
||||
|
||||
// Rewrite every binding in the new descriptor sets.
|
||||
VkDescriptorBufferInfo descriptorBuffers[UBUFFER_BINDING_COUNT];
|
||||
VkDescriptorImageInfo descriptorSamplers[SAMPLER_BINDING_COUNT];
|
||||
VkDescriptorImageInfo descriptorInputAttachments[INPUT_ATTACHMENT_COUNT];
|
||||
VkWriteDescriptorSet descriptorWrites[UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT +
|
||||
INPUT_ATTACHMENT_COUNT];
|
||||
uint32_t nwrites = 0;
|
||||
VkWriteDescriptorSet* writes = descriptorWrites;
|
||||
nwrites = 0;
|
||||
for (uint32_t binding = 0; binding < UBUFFER_BINDING_COUNT; binding++) {
|
||||
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
|
||||
if (mDescriptorRequirements.uniformBuffers[binding]) {
|
||||
VkDescriptorBufferInfo& bufferInfo = descriptorBuffers[binding];
|
||||
bufferInfo.buffer = mDescriptorRequirements.uniformBuffers[binding];
|
||||
bufferInfo.offset = mDescriptorRequirements.uniformBufferOffsets[binding];
|
||||
bufferInfo.range = mDescriptorRequirements.uniformBufferSizes[binding];
|
||||
|
||||
// We store size with 32 bits, so our "WHOLE" sentinel is different from Vk.
|
||||
if (bufferInfo.range == WHOLE_SIZE) {
|
||||
bufferInfo.range = VK_WHOLE_SIZE;
|
||||
}
|
||||
|
||||
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writeInfo.pNext = nullptr;
|
||||
writeInfo.dstArrayElement = 0;
|
||||
writeInfo.descriptorCount = 1;
|
||||
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
writeInfo.pImageInfo = nullptr;
|
||||
writeInfo.pBufferInfo = &bufferInfo;
|
||||
writeInfo.pTexelBufferView = nullptr;
|
||||
} else {
|
||||
writeInfo = mDummyBufferWriteInfo;
|
||||
assert_invariant(mDummyBufferWriteInfo.pBufferInfo->buffer);
|
||||
}
|
||||
assert_invariant(writeInfo.pBufferInfo->buffer);
|
||||
writeInfo.dstSet = descriptorCacheEntry.handles[0];
|
||||
writeInfo.dstBinding = binding;
|
||||
}
|
||||
for (uint32_t binding = 0; binding < SAMPLER_BINDING_COUNT; binding++) {
|
||||
if (mDescriptorRequirements.samplers[binding].sampler) {
|
||||
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
|
||||
VkDescriptorImageInfo& imageInfo = descriptorSamplers[binding];
|
||||
imageInfo = mDescriptorRequirements.samplers[binding];
|
||||
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writeInfo.pNext = nullptr;
|
||||
writeInfo.dstArrayElement = 0;
|
||||
writeInfo.descriptorCount = 1;
|
||||
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
writeInfo.pImageInfo = &imageInfo;
|
||||
writeInfo.pBufferInfo = nullptr;
|
||||
writeInfo.pTexelBufferView = nullptr;
|
||||
writeInfo.dstSet = descriptorCacheEntry.handles[1];
|
||||
writeInfo.dstBinding = binding;
|
||||
}
|
||||
}
|
||||
for (uint32_t binding = 0; binding < INPUT_ATTACHMENT_COUNT; binding++) {
|
||||
if (mDescriptorRequirements.inputAttachments[binding].imageView) {
|
||||
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
|
||||
VkDescriptorImageInfo& imageInfo = descriptorInputAttachments[binding];
|
||||
imageInfo = mDescriptorRequirements.inputAttachments[binding];
|
||||
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writeInfo.pNext = nullptr;
|
||||
writeInfo.dstArrayElement = 0;
|
||||
writeInfo.descriptorCount = 1;
|
||||
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
||||
writeInfo.pImageInfo = &imageInfo;
|
||||
writeInfo.pBufferInfo = nullptr;
|
||||
writeInfo.pTexelBufferView = nullptr;
|
||||
writeInfo.dstSet = descriptorCacheEntry.handles[2];
|
||||
writeInfo.dstBinding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
vkUpdateDescriptorSets(mDevice, nwrites, writes, 0, nullptr);
|
||||
|
||||
return &mDescriptorSets.emplace(mDescriptorRequirements, descriptorCacheEntry).first.value();
|
||||
commands->setPipeline(cacheEntry->handle);
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
|
||||
assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound.");
|
||||
|
||||
PipelineLayoutCacheEntry* layout = getOrCreatePipelineLayout();
|
||||
assert_invariant(layout);
|
||||
assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified");
|
||||
|
||||
VkPipelineShaderStageCreateInfo shaderStages[SHADER_MODULE_COUNT];
|
||||
shaderStages[0] = VkPipelineShaderStageCreateInfo{};
|
||||
@@ -404,7 +150,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {};
|
||||
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
pipelineCreateInfo.layout = layout->handle;
|
||||
pipelineCreateInfo.layout = mPipelineRequirements.layout;
|
||||
pipelineCreateInfo.renderPass = mPipelineRequirements.renderPass;
|
||||
pipelineCreateInfo.subpass = mPipelineRequirements.subpassIndex;
|
||||
pipelineCreateInfo.stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1;
|
||||
@@ -498,68 +244,6 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreatePipelineLayout() noexcept {
|
||||
auto iter = mPipelineLayouts.find(mPipelineRequirements.layout);
|
||||
if (UTILS_LIKELY(iter != mPipelineLayouts.end())) {
|
||||
return &iter.value();
|
||||
}
|
||||
|
||||
PipelineLayoutCacheEntry cacheEntry = {};
|
||||
|
||||
VkDescriptorSetLayoutBinding binding = {};
|
||||
binding.descriptorCount = 1; // NOTE: We never use arrays-of-blocks.
|
||||
binding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; // NOTE: This is potentially non-optimal.
|
||||
|
||||
// First create the descriptor set layout for UBO's.
|
||||
VkDescriptorSetLayoutBinding ubindings[UBUFFER_BINDING_COUNT];
|
||||
binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) {
|
||||
binding.binding = i;
|
||||
ubindings[i] = binding;
|
||||
}
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {};
|
||||
dlinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
dlinfo.bindingCount = UBUFFER_BINDING_COUNT;
|
||||
dlinfo.pBindings = ubindings;
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[0]);
|
||||
|
||||
// Next create the descriptor set layout for samplers.
|
||||
VkDescriptorSetLayoutBinding sbindings[SAMPLER_BINDING_COUNT];
|
||||
binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) {
|
||||
binding.stageFlags = getShaderStageFlags(mPipelineRequirements.layout, i);
|
||||
binding.binding = i;
|
||||
sbindings[i] = binding;
|
||||
}
|
||||
dlinfo.bindingCount = SAMPLER_BINDING_COUNT;
|
||||
dlinfo.pBindings = sbindings;
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[1]);
|
||||
|
||||
// Next create the descriptor set layout for input attachments.
|
||||
VkDescriptorSetLayoutBinding tbindings[INPUT_ATTACHMENT_COUNT];
|
||||
binding.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
||||
binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) {
|
||||
binding.binding = i;
|
||||
tbindings[i] = binding;
|
||||
}
|
||||
dlinfo.bindingCount = INPUT_ATTACHMENT_COUNT;
|
||||
dlinfo.pBindings = tbindings;
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[2]);
|
||||
|
||||
// Create VkPipelineLayout based on how to resources are bounded.
|
||||
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {};
|
||||
pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pPipelineLayoutCreateInfo.setLayoutCount = cacheEntry.descriptorSetLayouts.size();
|
||||
pPipelineLayoutCreateInfo.pSetLayouts = cacheEntry.descriptorSetLayouts.data();
|
||||
VkResult result = vkCreatePipelineLayout(mDevice, &pPipelineLayoutCreateInfo, VKALLOC,
|
||||
&cacheEntry.handle);
|
||||
if (UTILS_UNLIKELY(result != VK_SUCCESS)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &mPipelineLayouts.emplace(mPipelineRequirements.layout, cacheEntry).first.value();
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
|
||||
mPipelineRequirements.shaders[0] = program->getVertexShader();
|
||||
mPipelineRequirements.shaders[1] = program->getFragmentShader();
|
||||
@@ -600,97 +284,15 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding(
|
||||
uint32_t bindingIndex) const noexcept {
|
||||
auto& key = mDescriptorRequirements;
|
||||
return {
|
||||
key.uniformBuffers[bindingIndex],
|
||||
key.uniformBufferOffsets[bindingIndex],
|
||||
key.uniformBufferSizes[bindingIndex],
|
||||
};
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::unbindUniformBuffer(VkBuffer uniformBuffer) noexcept {
|
||||
auto& key = mDescriptorRequirements;
|
||||
for (uint32_t bindingIndex = 0u; bindingIndex < UBUFFER_BINDING_COUNT; ++bindingIndex) {
|
||||
if (key.uniformBuffers[bindingIndex] == uniformBuffer) {
|
||||
key.uniformBuffers[bindingIndex] = {};
|
||||
key.uniformBufferSizes[bindingIndex] = {};
|
||||
key.uniformBufferOffsets[bindingIndex] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept {
|
||||
for (auto& sampler : mDescriptorRequirements.samplers) {
|
||||
if (sampler.imageView == imageView) {
|
||||
sampler = {};
|
||||
}
|
||||
}
|
||||
for (auto& target : mDescriptorRequirements.inputAttachments) {
|
||||
if (target.imageView == imageView) {
|
||||
target = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex,
|
||||
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
VkBuffer buffer = bufferObject->buffer.getGpuBuffer();
|
||||
|
||||
ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT,
|
||||
"Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex,
|
||||
UBUFFER_BINDING_COUNT);
|
||||
auto& key = mDescriptorRequirements;
|
||||
key.uniformBuffers[bindingIndex] = buffer;
|
||||
|
||||
if (size == VK_WHOLE_SIZE) {
|
||||
size = WHOLE_SIZE;
|
||||
}
|
||||
|
||||
assert_invariant(offset <= 0xffffffffu);
|
||||
assert_invariant(size <= 0xffffffffu);
|
||||
|
||||
key.uniformBufferOffsets[bindingIndex] = offset;
|
||||
key.uniformBufferSizes[bindingIndex] = size;
|
||||
|
||||
mPipelineBoundResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
||||
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept {
|
||||
for (uint32_t bindingIndex = 0; bindingIndex < SAMPLER_BINDING_COUNT; bindingIndex++) {
|
||||
mDescriptorRequirements.samplers[bindingIndex] = samplers[bindingIndex];
|
||||
if (textures[bindingIndex]) {
|
||||
mPipelineBoundResources.acquire(textures[bindingIndex]);
|
||||
}
|
||||
}
|
||||
mPipelineRequirements.layout = flags;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindInputAttachment(uint32_t bindingIndex,
|
||||
VkDescriptorImageInfo targetInfo) noexcept {
|
||||
ASSERT_POSTCONDITION(bindingIndex < INPUT_ATTACHMENT_COUNT,
|
||||
"Input attachment bindings overflow: index = %d, capacity = %d.",
|
||||
bindingIndex, INPUT_ATTACHMENT_COUNT);
|
||||
mDescriptorRequirements.inputAttachments[bindingIndex] = targetInfo;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::terminate() noexcept {
|
||||
// Symmetric to createLayoutsAndDescriptors.
|
||||
destroyLayoutsAndDescriptors();
|
||||
for (auto& iter : mPipelines) {
|
||||
vkDestroyPipeline(mDevice, iter.second.handle, VKALLOC);
|
||||
}
|
||||
mPipelineBoundResources.clear();
|
||||
mPipelines.clear();
|
||||
mBoundPipeline = {};
|
||||
vmaDestroyBuffer(mAllocator, mDummyBuffer, mDummyMemory);
|
||||
mDummyBuffer = VK_NULL_HANDLE;
|
||||
mDummyMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
void VulkanPipelineCache::gc() noexcept {
|
||||
// The timestamp associated with a given cache entry represents "time" as a count of flush
|
||||
// events since the cache was constructed. If any cache entry was most recently used over
|
||||
// FVK_MAX_PIPELINE_AGE flush events in the past, then we can be sure that it is no longer
|
||||
@@ -700,194 +302,21 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
// The Vulkan spec says: "When a command buffer begins recording, all state in that command
|
||||
// buffer is undefined." Therefore, we need to clear all bindings at this time.
|
||||
mBoundPipeline = {};
|
||||
mBoundDescriptor = {};
|
||||
mCurrentScissor = {};
|
||||
|
||||
// NOTE: Due to robin_map restrictions, we cannot use auto or range-based loops.
|
||||
|
||||
// Check if any bundles in the cache are no longer in use by any command buffer. Descriptors
|
||||
// from unused bundles are moved back to their respective arenas.
|
||||
using ConstDescIterator = decltype(mDescriptorSets)::const_iterator;
|
||||
for (ConstDescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end();) {
|
||||
const DescriptorCacheEntry& cacheEntry = iter.value();
|
||||
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
auto& arenas = mPipelineLayouts[cacheEntry.pipelineLayout].descriptorSetArenas;
|
||||
for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) {
|
||||
arenas[i].push_back(cacheEntry.handles[i]);
|
||||
}
|
||||
++mDescriptorArenasCount;
|
||||
mDescriptorResources.erase(cacheEntry.id);
|
||||
iter = mDescriptorSets.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Evict any pipelines that have not been used in a while.
|
||||
// Any pipeline older than FVK_MAX_COMMAND_BUFFERS can be safely destroyed.
|
||||
using ConstPipeIterator = decltype(mPipelines)::const_iterator;
|
||||
for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) {
|
||||
const PipelineCacheEntry& cacheEntry = iter.value();
|
||||
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC);
|
||||
iter = mPipelines.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Evict any layouts that have not been used in a while.
|
||||
using ConstLayoutIterator = decltype(mPipelineLayouts)::const_iterator;
|
||||
for (ConstLayoutIterator iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end();) {
|
||||
const PipelineLayoutCacheEntry& cacheEntry = iter.value();
|
||||
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
vkDestroyPipelineLayout(mDevice, iter->second.handle, VKALLOC);
|
||||
for (auto setLayout : iter->second.descriptorSetLayouts) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
|
||||
PipelineLayoutKey key = iter.key();
|
||||
for (auto& pair : mDescriptorSets) {
|
||||
assert_invariant(pair.second.pipelineLayout != key);
|
||||
}
|
||||
#endif
|
||||
vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC);
|
||||
}
|
||||
auto& arenas = iter->second.descriptorSetArenas;
|
||||
assert_invariant(mDescriptorArenasCount >= arenas[0].size());
|
||||
mDescriptorArenasCount -= arenas[0].size();
|
||||
for (auto& arena : arenas) {
|
||||
vkFreeDescriptorSets(mDevice, mDescriptorPool, arena.size(), arena.data());
|
||||
}
|
||||
iter = mPipelineLayouts.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no descriptors from any extinct pool that are still in use, we can safely
|
||||
// destroy the extinct pools, which implicitly frees their associated descriptor sets.
|
||||
bool canPurgeExtinctPools = true;
|
||||
for (auto& bundle : mExtinctDescriptorBundles) {
|
||||
if (bundle.lastUsed + FVK_MAX_PIPELINE_AGE >= mCurrentTime) {
|
||||
canPurgeExtinctPools = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canPurgeExtinctPools) {
|
||||
for (VkDescriptorPool pool : mExtinctDescriptorPools) {
|
||||
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
|
||||
}
|
||||
mExtinctDescriptorPools.clear();
|
||||
|
||||
for (auto const& entry : mExtinctDescriptorBundles) {
|
||||
mDescriptorResources.erase(entry.id);
|
||||
}
|
||||
mExtinctDescriptorBundles.clear();
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorPool VulkanPipelineCache::createDescriptorPool(uint32_t size) const {
|
||||
VkDescriptorPoolSize poolSizes[DESCRIPTOR_TYPE_COUNT] = {};
|
||||
VkDescriptorPoolCreateInfo poolInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
|
||||
.maxSets = size * DESCRIPTOR_TYPE_COUNT,
|
||||
.poolSizeCount = DESCRIPTOR_TYPE_COUNT,
|
||||
.pPoolSizes = poolSizes
|
||||
};
|
||||
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
poolSizes[0].descriptorCount = poolInfo.maxSets * UBUFFER_BINDING_COUNT;
|
||||
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
poolSizes[1].descriptorCount = poolInfo.maxSets * SAMPLER_BINDING_COUNT;
|
||||
poolSizes[2].type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
||||
poolSizes[2].descriptorCount = poolInfo.maxSets * INPUT_ATTACHMENT_COUNT;
|
||||
|
||||
VkDescriptorPool pool;
|
||||
const UTILS_UNUSED VkResult result = vkCreateDescriptorPool(mDevice, &poolInfo, VKALLOC, &pool);
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
return pool;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept {
|
||||
// Our current descriptor set strategy can cause the # of descriptor sets to explode in certain
|
||||
// situations, so it's interesting to report the number that get stuffed into the cache.
|
||||
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
|
||||
utils::slog.d << "Destroying " << mDescriptorSets.size() << " bundles of descriptor sets."
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
mDescriptorSets.clear();
|
||||
|
||||
// Our current layout bundle strategy can cause the # of layout bundles to explode in certain
|
||||
// situations, so it's interesting to report the number that get stuffed into the cache.
|
||||
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
|
||||
utils::slog.d << "Destroying " << mPipelineLayouts.size() << " pipeline layouts."
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
for (auto& iter : mPipelineLayouts) {
|
||||
vkDestroyPipelineLayout(mDevice, iter.second.handle, VKALLOC);
|
||||
for (auto setLayout : iter.second.descriptorSetLayouts) {
|
||||
vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC);
|
||||
}
|
||||
// There is no need to free descriptor sets individually since destroying the VkDescriptorPool
|
||||
// implicitly frees them.
|
||||
}
|
||||
mPipelineLayouts.clear();
|
||||
vkDestroyDescriptorPool(mDevice, mDescriptorPool, VKALLOC);
|
||||
mDescriptorPool = VK_NULL_HANDLE;
|
||||
|
||||
for (VkDescriptorPool pool : mExtinctDescriptorPools) {
|
||||
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
|
||||
}
|
||||
mExtinctDescriptorPools.clear();
|
||||
mExtinctDescriptorBundles.clear();
|
||||
|
||||
// Both mDescriptorSets and mExtinctDescriptorBundles have been cleared, so it's safe to call
|
||||
// clear() on mDescriptorResources.
|
||||
mDescriptorResources.clear();
|
||||
|
||||
mBoundDescriptor = {};
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::growDescriptorPool() noexcept {
|
||||
// We need to destroy the old VkDescriptorPool, but we can't do so immediately because many
|
||||
// of its descriptors are still in use. So, stash it in an "extinct" list.
|
||||
mExtinctDescriptorPools.push_back(mDescriptorPool);
|
||||
|
||||
// Create the new VkDescriptorPool, twice as big as the old one.
|
||||
mDescriptorPoolSize *= 2;
|
||||
mDescriptorPool = createDescriptorPool(mDescriptorPoolSize);
|
||||
|
||||
// Clear out all unused descriptor sets in the arena so they don't get reclaimed. There is no
|
||||
// need to free them individually since the old VkDescriptorPool will be destroyed.
|
||||
for (auto iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end(); ++iter) {
|
||||
for (auto& arena : iter.value().descriptorSetArenas) {
|
||||
arena.clear();
|
||||
}
|
||||
}
|
||||
mDescriptorArenasCount = 0;
|
||||
|
||||
// Move all in-use descriptors from the primary cache into an "extinct" list, so that they will
|
||||
// later be destroyed rather than reclaimed.
|
||||
using DescIterator = decltype(mDescriptorSets)::iterator;
|
||||
for (DescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end(); ++iter) {
|
||||
mExtinctDescriptorBundles.push_back(iter.value());
|
||||
}
|
||||
mDescriptorSets.clear();
|
||||
}
|
||||
|
||||
size_t VulkanPipelineCache::PipelineLayoutKeyHashFn::operator()(
|
||||
const PipelineLayoutKey& key) const {
|
||||
std::hash<uint64_t> hasher;
|
||||
auto h0 = hasher(key.getBitsAt(0));
|
||||
auto h1 = hasher(key.getBitsAt(1));
|
||||
return h0 ^ (h1 << 1);
|
||||
}
|
||||
|
||||
bool VulkanPipelineCache::PipelineLayoutKeyEqual::operator()(const PipelineLayoutKey& k1,
|
||||
const PipelineLayoutKey& k2) const {
|
||||
return k1 == k2;
|
||||
using ConstPipeIterator = decltype(mPipelines)::const_iterator;
|
||||
for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) {
|
||||
const PipelineCacheEntry& cacheEntry = iter.value();
|
||||
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC);
|
||||
iter = mPipelines.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1,
|
||||
@@ -895,31 +324,6 @@ bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1,
|
||||
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(k1));
|
||||
}
|
||||
|
||||
bool VulkanPipelineCache::DescEqual::operator()(const DescriptorKey& k1,
|
||||
const DescriptorKey& k2) const {
|
||||
for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) {
|
||||
if (k1.uniformBuffers[i] != k2.uniformBuffers[i] ||
|
||||
k1.uniformBufferOffsets[i] != k2.uniformBufferOffsets[i] ||
|
||||
k1.uniformBufferSizes[i] != k2.uniformBufferSizes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) {
|
||||
if (k1.samplers[i].sampler != k2.samplers[i].sampler ||
|
||||
k1.samplers[i].imageView != k2.samplers[i].imageView ||
|
||||
k1.samplers[i].imageLayout != k2.samplers[i].imageLayout) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) {
|
||||
if (k1.inputAttachments[i].imageView != k2.inputAttachments[i].imageView ||
|
||||
k1.inputAttachments[i].imageLayout != k2.inputAttachments[i].imageLayout) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
@@ -34,12 +39,6 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
|
||||
VK_DEFINE_HANDLE(VmaAllocator)
|
||||
VK_DEFINE_HANDLE(VmaAllocation)
|
||||
VK_DEFINE_HANDLE(VmaPool)
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct VulkanProgram;
|
||||
@@ -56,32 +55,14 @@ class VulkanResourceAllocator;
|
||||
// - Assumes that viewport and scissor should be dynamic. (not baked into VkPipeline)
|
||||
// - Assumes that uniform buffers should be visible across all shader stages.
|
||||
//
|
||||
class VulkanPipelineCache : public CommandBufferObserver {
|
||||
class VulkanPipelineCache {
|
||||
public:
|
||||
VulkanPipelineCache(VulkanPipelineCache const&) = delete;
|
||||
VulkanPipelineCache& operator=(VulkanPipelineCache const&) = delete;
|
||||
|
||||
static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::UNIFORM_BINDING_COUNT;
|
||||
static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT;
|
||||
|
||||
// We assume only one possible input attachment between two subpasses. See also the subpasses
|
||||
// definition in VulkanFboCache.
|
||||
static constexpr uint32_t INPUT_ATTACHMENT_COUNT = 1;
|
||||
|
||||
static constexpr uint32_t SHADER_MODULE_COUNT = 2;
|
||||
static constexpr uint32_t VERTEX_ATTRIBUTE_COUNT = MAX_VERTEX_ATTRIBUTE_COUNT;
|
||||
|
||||
// Three descriptor set layouts: uniforms, combined image samplers, and input attachments.
|
||||
static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 3;
|
||||
static constexpr uint32_t INITIAL_DESCRIPTOR_SET_POOL_SIZE = 512;
|
||||
|
||||
// The VertexArray POD is an array of buffer targets and an array of attributes that refer to
|
||||
// those targets. It does not include any references to actual buffers, so you can think of it
|
||||
// as a vertex assembler configuration. For simplicity it contains fixed-size arrays and does
|
||||
// not store sizes; all unused entries are simply zeroed out.
|
||||
struct VertexArray {
|
||||
};
|
||||
|
||||
// The ProgramBundle contains weak references to the compiled vertex and fragment shaders.
|
||||
struct ProgramBundle {
|
||||
VkShaderModule vertex;
|
||||
@@ -89,10 +70,6 @@ public:
|
||||
VkSpecializationInfo* specializationInfos = nullptr;
|
||||
};
|
||||
|
||||
using UsageFlags = utils::bitset128;
|
||||
static UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags stages, UsageFlags src = {});
|
||||
static UsageFlags disableUsageFlags(uint16_t binding, UsageFlags src);
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic warning "-Wpadded"
|
||||
|
||||
@@ -135,62 +112,26 @@ public:
|
||||
|
||||
// Upon construction, the pipeCache initializes some internal state but does not make any Vulkan
|
||||
// calls. On destruction it will free any cached Vulkan objects that haven't already been freed.
|
||||
VulkanPipelineCache(VulkanResourceAllocator* allocator);
|
||||
VulkanPipelineCache(VkDevice device, VmaAllocator allocator);
|
||||
~VulkanPipelineCache();
|
||||
void setDevice(VkDevice device, VmaAllocator allocator);
|
||||
|
||||
// Creates new descriptor sets if necessary and binds them using vkCmdBindDescriptorSets.
|
||||
// Returns false if descriptor set allocation fails.
|
||||
bool bindDescriptors(VkCommandBuffer cmdbuffer) noexcept;
|
||||
void bindLayout(VkPipelineLayout layout) noexcept;
|
||||
|
||||
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
|
||||
// Returns false if an error occurred.
|
||||
bool bindPipeline(VulkanCommandBuffer* commands) noexcept;
|
||||
|
||||
// Sets up a new scissor rectangle if it has been dirtied.
|
||||
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
|
||||
void bindPipeline(VulkanCommandBuffer* commands);
|
||||
|
||||
// Each of the following methods are fast and do not make Vulkan calls.
|
||||
void bindProgram(VulkanProgram* program) noexcept;
|
||||
void bindRasterState(const RasterState& rasterState) noexcept;
|
||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||
void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
|
||||
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
|
||||
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
||||
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
|
||||
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
|
||||
|
||||
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
|
||||
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
|
||||
|
||||
// Gets the current UBO at the given slot, useful for push / pop.
|
||||
UniformBufferBinding getUniformBufferBinding(uint32_t bindingIndex) const noexcept;
|
||||
|
||||
// Checks if the given uniform is bound to any slot, and if so binds "null" to that slot.
|
||||
// Also invalidates all cached descriptors that refer to the given buffer.
|
||||
// This is only necessary when the client knows that the UBO is about to be destroyed.
|
||||
void unbindUniformBuffer(VkBuffer uniformBuffer) noexcept;
|
||||
|
||||
// Checks if an image view is bound to any sampler, and if so resets that particular slot.
|
||||
// Also invalidates all cached descriptors that refer to the given image view.
|
||||
// This is only necessary when the client knows that a texture is about to be destroyed.
|
||||
void unbindImageView(VkImageView imageView) noexcept;
|
||||
|
||||
// NOTE: In theory we should proffer "unbindSampler" but in practice we never destroy samplers.
|
||||
|
||||
// Destroys all managed Vulkan objects. This should be called before changing the VkDevice.
|
||||
void terminate() noexcept;
|
||||
|
||||
// vkCmdBindPipeline and vkCmdBindDescriptorSets establish bindings to a specific command
|
||||
// buffer; they are not global to the device. Therefore we need to be notified when a
|
||||
// new command buffer becomes active.
|
||||
void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) override;
|
||||
|
||||
// Injects a dummy texture that can be used to clear out old descriptor sets.
|
||||
void setDummyTexture(VkImageView imageView) {
|
||||
mDummyTargetInfo.imageView = imageView;
|
||||
}
|
||||
|
||||
static VkPrimitiveTopology getPrimitiveTopology(PrimitiveType pt) noexcept {
|
||||
switch (pt) {
|
||||
case PrimitiveType::POINTS:
|
||||
@@ -206,22 +147,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void gc() noexcept;
|
||||
|
||||
private:
|
||||
// PIPELINE LAYOUT CACHE KEY
|
||||
// -------------------------
|
||||
|
||||
using PipelineLayoutKey = utils::bitset128;
|
||||
|
||||
static_assert(PipelineLayoutKey::BIT_COUNT >= 2 * MAX_SAMPLER_COUNT);
|
||||
|
||||
struct PipelineLayoutKeyHashFn {
|
||||
size_t operator()(const PipelineLayoutKey& key) const;
|
||||
};
|
||||
|
||||
struct PipelineLayoutKeyEqual {
|
||||
bool operator()(const PipelineLayoutKey& k1, const PipelineLayoutKey& k2) const;
|
||||
};
|
||||
|
||||
// PIPELINE CACHE KEY
|
||||
// ------------------
|
||||
|
||||
@@ -274,10 +202,10 @@ private:
|
||||
VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156
|
||||
RasterState rasterState; // 16 : 284
|
||||
uint32_t padding; // 4 : 300
|
||||
PipelineLayoutKey layout; // 16 : 304
|
||||
VkPipelineLayout layout; // 8 : 304
|
||||
};
|
||||
|
||||
static_assert(sizeof(PipelineKey) == 320, "PipelineKey must not have implicit padding.");
|
||||
static_assert(sizeof(PipelineKey) == 312, "PipelineKey must not have implicit padding.");
|
||||
|
||||
using PipelineHashFn = utils::hash::MurmurHashFn<PipelineKey>;
|
||||
|
||||
@@ -285,52 +213,6 @@ private:
|
||||
bool operator()(const PipelineKey& k1, const PipelineKey& k2) const;
|
||||
};
|
||||
|
||||
// DESCRIPTOR SET CACHE KEY
|
||||
// ------------------------
|
||||
|
||||
// Equivalent to VkDescriptorImageInfo but with explicit padding.
|
||||
struct DescriptorImageInfo {
|
||||
DescriptorImageInfo& operator=(const VkDescriptorImageInfo& that) {
|
||||
sampler = that.sampler;
|
||||
imageView = that.imageView;
|
||||
imageLayout = that.imageLayout;
|
||||
padding = 0;
|
||||
return *this;
|
||||
}
|
||||
operator VkDescriptorImageInfo() const { return { sampler, imageView, imageLayout }; }
|
||||
|
||||
// TODO: replace the 64-bit sampler handle with `uint32_t samplerParams` and remove the
|
||||
// padding field. This is possible if we have access to the VulkanSamplerCache.
|
||||
VkSampler sampler;
|
||||
|
||||
VkImageView imageView;
|
||||
VkImageLayout imageLayout;
|
||||
uint32_t padding;
|
||||
};
|
||||
|
||||
// We store size with 32 bits, so our "WHOLE" sentinel is different from Vk.
|
||||
static const uint32_t WHOLE_SIZE = 0xffffffffu;
|
||||
|
||||
// Represents all the Vulkan state that comprises a bound descriptor set.
|
||||
struct DescriptorKey {
|
||||
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 80 0
|
||||
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 1488 80
|
||||
DescriptorImageInfo inputAttachments[INPUT_ATTACHMENT_COUNT]; // 24 1568
|
||||
uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 40 1592
|
||||
uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 40 1632
|
||||
};
|
||||
static_assert(offsetof(DescriptorKey, samplers) == 80);
|
||||
static_assert(offsetof(DescriptorKey, inputAttachments) == 1568);
|
||||
static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1592);
|
||||
static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1632);
|
||||
static_assert(sizeof(DescriptorKey) == 1672, "DescriptorKey must not have implicit padding.");
|
||||
|
||||
using DescHashFn = utils::hash::MurmurHashFn<DescriptorKey>;
|
||||
|
||||
struct DescEqual {
|
||||
bool operator()(const DescriptorKey& k1, const DescriptorKey& k2) const;
|
||||
};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// CACHE ENTRY STRUCTS
|
||||
@@ -343,16 +225,6 @@ private:
|
||||
using Timestamp = uint64_t;
|
||||
Timestamp mCurrentTime = 0;
|
||||
|
||||
// The descriptor set cache entry is a group of descriptor sets that are bound simultaneously.
|
||||
struct DescriptorCacheEntry {
|
||||
std::array<VkDescriptorSet, DESCRIPTOR_TYPE_COUNT> handles;
|
||||
Timestamp lastUsed;
|
||||
PipelineLayoutKey pipelineLayout;
|
||||
uint32_t id;
|
||||
};
|
||||
uint32_t mDescriptorCacheEntryCount = 0;
|
||||
|
||||
|
||||
struct PipelineCacheEntry {
|
||||
VkPipeline handle;
|
||||
Timestamp lastUsed;
|
||||
@@ -361,98 +233,33 @@ private:
|
||||
struct PipelineLayoutCacheEntry {
|
||||
VkPipelineLayout handle;
|
||||
Timestamp lastUsed;
|
||||
|
||||
std::array<VkDescriptorSetLayout, DESCRIPTOR_TYPE_COUNT> descriptorSetLayouts;
|
||||
|
||||
// Each pipeline layout has 3 arenas of unused descriptors (one for each binding type).
|
||||
//
|
||||
// The difference between the "arenas" and the "pool" are as follows.
|
||||
//
|
||||
// - The "pool" is a single, centralized factory for all descriptors (VkDescriptorPool).
|
||||
//
|
||||
// - Each "arena" is a set of unused (but alive) descriptors that can only be used with a
|
||||
// specific pipeline layout and a specific binding type. We manually manage each arena.
|
||||
// The arenas are created in an empty state, and they are gradually populated as new
|
||||
// descriptors are reclaimed over time. This is quite different from the pool, which is
|
||||
// given a fixed size when it is constructed.
|
||||
//
|
||||
std::array<std::vector<VkDescriptorSet>, DESCRIPTOR_TYPE_COUNT> descriptorSetArenas;
|
||||
};
|
||||
|
||||
// CACHE CONTAINERS
|
||||
// ----------------
|
||||
|
||||
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey , PipelineLayoutCacheEntry,
|
||||
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
|
||||
using PipelineMap = tsl::robin_map<PipelineKey, PipelineCacheEntry,
|
||||
PipelineHashFn, PipelineEqual>;
|
||||
using DescriptorMap
|
||||
= tsl::robin_map<DescriptorKey, DescriptorCacheEntry, DescHashFn, DescEqual>;
|
||||
using DescriptorResourceMap
|
||||
= std::unordered_map<uint32_t, std::unique_ptr<VulkanAcquireOnlyResourceManager>>;
|
||||
|
||||
PipelineLayoutMap mPipelineLayouts;
|
||||
private:
|
||||
|
||||
PipelineCacheEntry* getOrCreatePipeline() noexcept;
|
||||
|
||||
PipelineMap mPipelines;
|
||||
DescriptorMap mDescriptorSets;
|
||||
DescriptorResourceMap mDescriptorResources;
|
||||
|
||||
// These helpers all return unstable pointers that should not be stored.
|
||||
DescriptorCacheEntry* createDescriptorSets() noexcept;
|
||||
PipelineCacheEntry* createPipeline() noexcept;
|
||||
PipelineLayoutCacheEntry* getOrCreatePipelineLayout() noexcept;
|
||||
|
||||
// Misc helper methods.
|
||||
void destroyLayoutsAndDescriptors() noexcept;
|
||||
VkDescriptorPool createDescriptorPool(uint32_t size) const;
|
||||
void growDescriptorPool() noexcept;
|
||||
|
||||
// Immutable state.
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||
|
||||
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
DescriptorKey mDescriptorRequirements = {};
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
DescriptorKey mBoundDescriptor = {};
|
||||
|
||||
// Current state for scissoring.
|
||||
VkRect2D mCurrentScissor = {};
|
||||
|
||||
// The descriptor set pool starts out with a decent number of descriptor sets. The cache can
|
||||
// grow the pool by re-creating it with a larger size. See growDescriptorPool().
|
||||
VkDescriptorPool mDescriptorPool;
|
||||
|
||||
// This describes the number of descriptor sets in mDescriptorPool. Note that this needs to be
|
||||
// multiplied by DESCRIPTOR_TYPE_COUNT to get the actual number of descriptor sets. Also note
|
||||
// that the number of low-level "descriptors" (not descriptor *sets*) is actually much more than
|
||||
// this size. It can be computed only by factoring in UBUFFER_BINDING_COUNT etc.
|
||||
uint32_t mDescriptorPoolSize = INITIAL_DESCRIPTOR_SET_POOL_SIZE;
|
||||
|
||||
// To get the actual number of descriptor sets that have been allocated from the pool,
|
||||
// take the sum of mDescriptorArenasCount (these are inactive descriptor sets) and the
|
||||
// number of entries in the mDescriptorPool map (active descriptor sets). Multiply the result by
|
||||
// DESCRIPTOR_TYPE_COUNT.
|
||||
uint32_t mDescriptorArenasCount = 0;
|
||||
|
||||
// After a growth event (i.e. when the VkDescriptorPool is replaced with a bigger version), all
|
||||
// currently used descriptors are moved into the "extinct" sets so that they can be safely
|
||||
// destroyed a few frames later.
|
||||
std::list<VkDescriptorPool> mExtinctDescriptorPools;
|
||||
std::list<DescriptorCacheEntry> mExtinctDescriptorBundles;
|
||||
|
||||
VkDescriptorBufferInfo mDummyBufferInfo = {};
|
||||
VkWriteDescriptorSet mDummyBufferWriteInfo = {};
|
||||
VkDescriptorImageInfo mDummyTargetInfo = {};
|
||||
VkWriteDescriptorSet mDummyTargetWriteInfo = {};
|
||||
|
||||
VkBuffer mDummyBuffer;
|
||||
VmaAllocation mDummyMemory;
|
||||
|
||||
VulkanResourceAllocator* mResourceAllocator;
|
||||
VulkanAcquireOnlyResourceManager mPipelineBoundResources;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -28,7 +28,6 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using ImgUtil = VulkanImageUtility;
|
||||
using TaskHandler = VulkanReadPixels::TaskHandler;
|
||||
using WorkloadFunc = TaskHandler::WorkloadFunc;
|
||||
using OnCompleteFunc = TaskHandler::OnCompleteFunc;
|
||||
@@ -118,7 +117,7 @@ void VulkanReadPixels::terminate() noexcept {
|
||||
VulkanReadPixels::VulkanReadPixels(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x, uint32_t const y,
|
||||
void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint32_t const y,
|
||||
uint32_t const width, uint32_t const height, uint32_t const graphicsQueueFamilyIndex,
|
||||
PixelBufferDescriptor&& pbd, SelecteMemoryFunction const& selectMemoryFunc,
|
||||
OnReadCompleteFunction const& readCompleteFunc) {
|
||||
@@ -218,7 +217,7 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
|
||||
};
|
||||
vkBeginCommandBuffer(cmdbuffer, &binfo);
|
||||
|
||||
ImgUtil::transitionLayout(cmdbuffer, {
|
||||
imgutil::transitionLayout(cmdbuffer, {
|
||||
.image = stagingImage,
|
||||
.oldLayout = VulkanLayout::UNDEFINED,
|
||||
.newLayout = VulkanLayout::TRANSFER_DST,
|
||||
@@ -232,8 +231,7 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
|
||||
});
|
||||
|
||||
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
|
||||
const VkImageSubresourceRange srcRange
|
||||
= srcAttachment.getSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange();
|
||||
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
|
||||
VkImageCopy const imageCopyRegion = {
|
||||
@@ -266,8 +264,8 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
|
||||
imageCopyRegion.srcOffset.y + imageCopyRegion.extent.height <= srcExtent.height);
|
||||
|
||||
vkCmdCopyImage(cmdbuffer, srcAttachment.getImage(),
|
||||
ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC), stagingImage,
|
||||
ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
|
||||
imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC), stagingImage,
|
||||
imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion);
|
||||
|
||||
// Restore the source image layout.
|
||||
srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::COLOR_ATTACHMENT);
|
||||
|
||||
@@ -26,9 +26,7 @@
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -74,8 +72,8 @@ public:
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
void run(VulkanRenderTarget const* srcTarget, uint32_t x, uint32_t y, uint32_t width,
|
||||
uint32_t height, uint32_t graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
|
||||
void run(VulkanRenderTarget* srcTarget, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
|
||||
uint32_t graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
|
||||
SelecteMemoryFunction const& selectMemoryFunc,
|
||||
OnReadCompleteFunction const& readCompleteFunc);
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace filament::backend {
|
||||
#endif
|
||||
|
||||
class VulkanResourceAllocator {
|
||||
|
||||
public:
|
||||
using AllocatorImpl = HandleAllocatorVK;
|
||||
VulkanResourceAllocator(size_t arenaSize, bool disableUseAfterFreeCheck)
|
||||
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck)
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
HandleAllocatorVK mHandleAllocatorImpl;
|
||||
AllocatorImpl mHandleAllocatorImpl;
|
||||
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
public:
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "caching/VulkanDescriptorSet.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -62,6 +62,9 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t
|
||||
case VulkanResourceType::RENDER_PRIMITIVE:
|
||||
allocator->destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
|
||||
break;
|
||||
case VulkanResourceType::DESCRIPTOR_SET_LAYOUT:
|
||||
allocator->destruct<VulkanDescriptorSetLayout>(Handle<VulkanDescriptorSetLayout>(id));
|
||||
break;
|
||||
case VulkanResourceType::DESCRIPTOR_SET:
|
||||
allocator->destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
|
||||
break;
|
||||
|
||||
@@ -35,24 +35,25 @@ struct VulkanThreadSafeResource;
|
||||
|
||||
// Subclasses of VulkanResource must provide this enum in their construction.
|
||||
enum class VulkanResourceType : uint8_t {
|
||||
BUFFER_OBJECT,
|
||||
INDEX_BUFFER,
|
||||
PROGRAM,
|
||||
RENDER_TARGET,
|
||||
SAMPLER_GROUP,
|
||||
SWAP_CHAIN,
|
||||
RENDER_PRIMITIVE,
|
||||
TEXTURE,
|
||||
TIMER_QUERY,
|
||||
VERTEX_BUFFER,
|
||||
VERTEX_BUFFER_INFO,
|
||||
DESCRIPTOR_SET,
|
||||
BUFFER_OBJECT = 0,
|
||||
INDEX_BUFFER = 1,
|
||||
PROGRAM = 2,
|
||||
RENDER_TARGET = 3,
|
||||
SAMPLER_GROUP = 4,
|
||||
SWAP_CHAIN = 5,
|
||||
RENDER_PRIMITIVE = 6,
|
||||
TEXTURE = 7,
|
||||
TIMER_QUERY = 8,
|
||||
VERTEX_BUFFER = 9,
|
||||
VERTEX_BUFFER_INFO = 10,
|
||||
DESCRIPTOR_SET_LAYOUT = 11,
|
||||
DESCRIPTOR_SET = 12,
|
||||
|
||||
// Below are resources that are managed manually (i.e. not ref counted).
|
||||
FENCE,
|
||||
HEAP_ALLOCATED,
|
||||
FENCE = 13,
|
||||
HEAP_ALLOCATED = 14,
|
||||
|
||||
END_TYPE, // A placeholder
|
||||
END_TYPE = 15, // A placeholder
|
||||
};
|
||||
|
||||
#define IS_HEAP_ALLOC_TYPE(f) \
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "VulkanStagePool.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
@@ -118,7 +119,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
// VK_IMAGE_LAYOUT_PREINITIALIZED or VK_IMAGE_LAYOUT_GENERAL layout. Calling
|
||||
// vkGetImageSubresourceLayout for a linear image returns a subresource layout mapping that is
|
||||
// valid for either of those image layouts."
|
||||
VulkanImageUtility::transitionLayout(cmdbuffer, {
|
||||
imgutil::transitionLayout(cmdbuffer, {
|
||||
.image = image->image,
|
||||
.oldLayout = VulkanLayout::UNDEFINED,
|
||||
.newLayout = VulkanLayout::READ_WRITE, // (= VK_IMAGE_LAYOUT_GENERAL)
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user