Compare commits
111
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0404be5127 | ||
|
|
a784fb7d8b | ||
|
|
8459cbc91c | ||
|
|
344c04a2d8 | ||
|
|
55e2691f3f | ||
|
|
7be9cdc7f8 | ||
|
|
af792e3d18 | ||
|
|
f93677548d | ||
|
|
1eff66e4ec | ||
|
|
34f8b9aa20 | ||
|
|
6d07443188 | ||
|
|
13a23703ad | ||
|
|
57f6ca625c | ||
|
|
02f5903b67 | ||
|
|
c39a870abc | ||
|
|
09b8008e17 | ||
|
|
59b59cf6be | ||
|
|
0df6013263 | ||
|
|
d5041fced7 | ||
|
|
9c54b8a777 | ||
|
|
8a534d0940 | ||
|
|
130825422e | ||
|
|
984006ee25 | ||
|
|
b676002521 | ||
|
|
8bdf7bd1e5 | ||
|
|
1262cb286c | ||
|
|
ee6f3fb1dc | ||
|
|
b9a9586abb | ||
|
|
6ee20a57aa | ||
|
|
368fa2bf39 | ||
|
|
8f642892b4 | ||
|
|
5435a8ed3b | ||
|
|
5fd7a4e153 | ||
|
|
20ff230b92 | ||
|
|
44ff79ad34 | ||
|
|
102d2db008 | ||
|
|
5c9039e650 | ||
|
|
1203c24f06 | ||
|
|
9704d27aeb | ||
|
|
c6f2c3fc1c | ||
|
|
2faf868341 | ||
|
|
81f6260843 | ||
|
|
1a9063d53a | ||
|
|
6062b3c8c6 | ||
|
|
d9186c44ba | ||
|
|
6d06e00934 | ||
|
|
b4021a1fbc | ||
|
|
2c9559f9b8 | ||
|
|
e2da13f817 | ||
|
|
addae314b4 | ||
|
|
5e3c51822d | ||
|
|
9d817a0cc9 | ||
|
|
95ecc3d1e4 | ||
|
|
cabaa4323e | ||
|
|
d6b2ec49b0 | ||
|
|
93004aa5bc | ||
|
|
27fa8ab46d | ||
|
|
614990f413 | ||
|
|
da058028fd | ||
|
|
55c75453df | ||
|
|
f5eff10532 | ||
|
|
56a0364534 | ||
|
|
c8aae3cdaa | ||
|
|
4f94c575c9 | ||
|
|
1a29f020c9 | ||
|
|
d5847a3c6a | ||
|
|
3dbae482d3 | ||
|
|
27572e3be6 | ||
|
|
dedf276e26 | ||
|
|
111ad96134 | ||
|
|
2a67152dda | ||
|
|
2f62978e4c | ||
|
|
fdb90f2f49 | ||
|
|
5418b8c4cc | ||
|
|
724cab3623 | ||
|
|
d9faea264a | ||
|
|
6116c2f9eb | ||
|
|
95fdba0c5d | ||
|
|
9c56517370 | ||
|
|
08f06f4298 | ||
|
|
b1794a82ca | ||
|
|
56bd2871ec | ||
|
|
5a9c47bcb5 | ||
|
|
51c2fa2728 | ||
|
|
334c71de52 | ||
|
|
001098098b | ||
|
|
35a00b1d84 | ||
|
|
b517a45213 | ||
|
|
f8057b9a5b | ||
|
|
d9ee526a72 | ||
|
|
164d7507bb | ||
|
|
b007e9137e | ||
|
|
06bae26e1b | ||
|
|
1be3e13427 | ||
|
|
e7c67d1adb | ||
|
|
9b67d80961 | ||
|
|
b066678a37 | ||
|
|
b10d5c58aa | ||
|
|
000bff5ce1 | ||
|
|
47d45a64fa | ||
|
|
2bd48f58ff | ||
|
|
f2f4f556b5 | ||
|
|
5aa15f061b | ||
|
|
63db4f0bf0 | ||
|
|
7797f5af38 | ||
|
|
f9def09d17 | ||
|
|
494934f34e | ||
|
|
caae42fdf6 | ||
|
|
8f6c4841cb | ||
|
|
5d0c06e3f2 | ||
|
|
7d31a7f7ea |
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
jobs:
|
||||
cocoapods-deploy:
|
||||
name: cocoapods-deploy
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Check out iOS/CocoaPods directory
|
||||
uses: Bhacaz/checkout-files@49fc3050859046bf4f4873678d46099985640e89
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
npm-deploy:
|
||||
name: npm-deploy
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-22.04]
|
||||
os: [macos-14, ubuntu-22.04]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-22.04]
|
||||
os: [macos-14, ubuntu-22.04]
|
||||
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
|
||||
|
||||
steps:
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
||||
|
||||
steps:
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'ios'
|
||||
|
||||
steps:
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
|
||||
+32
-5
@@ -41,6 +41,8 @@ option(FILAMENT_LINUX_IS_MOBILE "Treat Linux as Mobile" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_ASAN_UBSAN "Enable Address and Undefined Behavior Sanitizers" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_TSAN "Enable Thread Sanitizer" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
|
||||
|
||||
set(FILAMENT_NDK_VERSION "" CACHE STRING
|
||||
@@ -230,6 +232,21 @@ if (WIN32)
|
||||
# we don't need them on CI.
|
||||
string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" ${LinkerFlag} ${${LinkerFlag}})
|
||||
endforeach()
|
||||
|
||||
# We turn off compile-time optimizations for CI, as options that speed up the compile-time
|
||||
# (e.g. /MP) might increase memory usage, leading to instabilities on limited CI machines.
|
||||
option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" OFF)
|
||||
else()
|
||||
option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" ON)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
if (FILAMENT_SHORTEN_MSVC_COMPILATION)
|
||||
# enable multi-processor compilation
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
# disable run-time STL checks to improve tools (e.g. matc) performance
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_ITERATOR_DEBUG_LEVEL=0")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -411,7 +428,13 @@ endif()
|
||||
if (FILAMENT_ENABLE_ASAN_UBSAN)
|
||||
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=address -fsanitize=undefined")
|
||||
endif()
|
||||
|
||||
if (FILAMENT_ENABLE_TSAN)
|
||||
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=thread")
|
||||
endif()
|
||||
if (ANDROID)
|
||||
# keep STL debug infos (mimics what the NDK does)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-limit-debug-info")
|
||||
endif()
|
||||
if (NOT MSVC AND NOT WEBGL)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstack-protector")
|
||||
endif()
|
||||
@@ -611,9 +634,9 @@ function(combine_static_libs TARGET OUTPUT DEPS)
|
||||
# Loop through the dependent libraries and query their location on disk.
|
||||
set(DEPS_FILES )
|
||||
foreach(DEPENDENCY ${DEPS})
|
||||
if(TARGET ${DEPENDENCY})
|
||||
if (TARGET ${DEPENDENCY})
|
||||
get_property(dep_type TARGET ${DEPENDENCY} PROPERTY TYPE)
|
||||
if(dep_type STREQUAL "STATIC_LIBRARY")
|
||||
if (dep_type STREQUAL "STATIC_LIBRARY")
|
||||
list(APPEND DEPS_FILES "$<TARGET_FILE:${DEPENDENCY}>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -683,7 +706,7 @@ function(get_resgen_vars ARCHIVE_DIR ARCHIVE_NAME)
|
||||
set(RESGEN_OUTPUTS "${OUTPUTS}" PARENT_SCOPE)
|
||||
set(RESGEN_FLAGS -qx ${ARCHIVE_DIR} -p ${ARCHIVE_NAME} PARENT_SCOPE)
|
||||
set(RESGEN_SOURCE "${ARCHIVE_DIR}/${ARCHIVE_NAME}${ASM_SUFFIX}.S" PARENT_SCOPE)
|
||||
set(RESGEN_SOURCE_FLAGS "-I${ARCHIVE_DIR} ${ASM_ARCH_FLAG}" PARENT_SCOPE)
|
||||
set(RESGEN_SOURCE_FLAGS "-I'${ARCHIVE_DIR}' ${ASM_ARCH_FLAG}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -698,7 +721,6 @@ add_subdirectory(${LIBRARIES}/filabridge)
|
||||
add_subdirectory(${LIBRARIES}/filaflat)
|
||||
add_subdirectory(${LIBRARIES}/filagui)
|
||||
add_subdirectory(${LIBRARIES}/filameshio)
|
||||
add_subdirectory(${LIBRARIES}/geometry)
|
||||
add_subdirectory(${LIBRARIES}/gltfio)
|
||||
add_subdirectory(${LIBRARIES}/ibl)
|
||||
add_subdirectory(${LIBRARIES}/iblprefilter)
|
||||
@@ -726,6 +748,9 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
|
||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||
add_subdirectory(${LIBRARIES}/geometry)
|
||||
|
||||
if (FILAMENT_BUILD_FILAMAT OR IS_HOST_PLATFORM)
|
||||
# spirv-tools must come before filamat, as filamat relies on the presence of the
|
||||
# spirv-tools_SOURCE_DIR variable.
|
||||
@@ -743,6 +768,8 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
add_subdirectory(${LIBRARIES}/bluevk)
|
||||
add_subdirectory(${EXTERNAL}/vkmemalloc/tnt)
|
||||
set(SPIRV_HEADERS_SKIP_EXAMPLES ON)
|
||||
add_subdirectory(${EXTERNAL}/spirv-headers)
|
||||
endif()
|
||||
|
||||
set(FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples)
|
||||
|
||||
@@ -7,6 +7,3 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- matc: New option `-1` to disable generation of ESSL 1.0 code in Feature Level 0 materials
|
||||
- matc: Support optimizations for ESSL 1.0 code [⚠️ **Recompile materials**]
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.47.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.50.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.47.0'
|
||||
pod 'Filament', '~> 1.50.2'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,7 +7,42 @@ 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.50.3
|
||||
|
||||
|
||||
## v1.50.2
|
||||
|
||||
|
||||
## v1.50.1
|
||||
|
||||
- Metal: fix some shader artifacts by disabling fast math optimizations.
|
||||
- backend: remove `atan2` overload which had a typo and wasn't useful. Fixes b/320856413.
|
||||
- utils: remove usages of `SpinLock`. Fixes b/321101014.
|
||||
|
||||
## v1.50.0
|
||||
- engine: TAA now supports 4x upscaling [BETA] [⚠️ **New Material Version**]
|
||||
|
||||
## v1.49.3
|
||||
|
||||
- matc: Generate stereo variants for FL0 materials [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.49.2
|
||||
|
||||
|
||||
## v1.49.1
|
||||
|
||||
|
||||
## v1.49.0
|
||||
|
||||
- matc: Fix ESSL 1.0 codegen when using external samplers [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.48.0
|
||||
|
||||
- matc: New option `-1` to disable generation of ESSL 1.0 code in Feature Level 0 materials
|
||||
- matc: Support optimizations for ESSL 1.0 code [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.47.0
|
||||
|
||||
- engine: Support up to 4 side-by-side stereoscopic eyes, configurable at Engine creation time. See
|
||||
`Engine::Config::stereoscopicEyeCount`. [⚠️ **Recompile Materials**]
|
||||
|
||||
|
||||
@@ -81,14 +81,14 @@ buildscript {
|
||||
ext.versions = [
|
||||
'jdk': 17,
|
||||
'minSdk': 19,
|
||||
'targetSdk': 33,
|
||||
'compileSdk': 33,
|
||||
'kotlin': '1.9.0',
|
||||
'kotlin_coroutines': '1.7.2',
|
||||
'targetSdk': 34,
|
||||
'compileSdk': 34,
|
||||
'kotlin': '1.9.21',
|
||||
'kotlin_coroutines': '1.7.3',
|
||||
'buildTools': '34.0.0',
|
||||
'ndk': '25.1.8937393',
|
||||
'androidx_core': '1.10.1',
|
||||
'androidx_annotations': '1.6.0'
|
||||
'ndk': '26.1.10909125',
|
||||
'androidx_core': '1.12.0',
|
||||
'androidx_annotations': '1.7.0'
|
||||
]
|
||||
|
||||
ext.deps = [
|
||||
@@ -104,7 +104,7 @@ buildscript {
|
||||
]
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
|
||||
}
|
||||
|
||||
|
||||
@@ -435,6 +435,13 @@ Java_com_google_android_filament_Engine_nIsAutomaticInstancingEnabled(JNIEnv*, j
|
||||
return (jboolean)engine->isAutomaticInstancingEnabled();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Engine_nGetMaxStereoscopicEyes(JNIEnv*, jclass, jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jlong) engine->getMaxStereoscopicEyes();
|
||||
}
|
||||
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Engine_nGetSupportedFeatureLevel(JNIEnv *, jclass,
|
||||
jlong nativeEngine) {
|
||||
@@ -477,7 +484,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*,
|
||||
jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB,
|
||||
jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB,
|
||||
jlong jobSystemThreadCount) {
|
||||
jlong jobSystemThreadCount, jlong stereoscopicEyeCount,
|
||||
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
|
||||
@@ -486,6 +494,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
|
||||
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
|
||||
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
|
||||
.stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount,
|
||||
.resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB,
|
||||
.resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge,
|
||||
};
|
||||
builder->config(&config);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ Java_com_google_android_filament_EntityManager_nCreateArray(JNIEnv* env, jclass,
|
||||
// (which it is), but still.
|
||||
em->create((size_t) n, reinterpret_cast<Entity *>(entities));
|
||||
|
||||
env->ReleaseIntArrayElements(entities_, entities, 0);
|
||||
env->ReleaseIntArrayElements(entities_, entities, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -205,7 +205,7 @@ Java_com_google_android_filament_MaterialInstance_nSetIntParameterArray(JNIEnv *
|
||||
break;
|
||||
}
|
||||
|
||||
env->ReleaseIntArrayElements(v_, v, 0);
|
||||
env->ReleaseIntArrayElements(v_, v, JNI_ABORT);
|
||||
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,6 @@ Java_com_google_android_filament_Scene_nGetEntities(JNIEnv *env, jclass ,
|
||||
out[i++] = (jint) entity.getId();
|
||||
}
|
||||
});
|
||||
env->ReleaseIntArrayElements(outArray, (jint*) out, JNI_ABORT);
|
||||
env->ReleaseIntArrayElements(outArray, (jint*) out, 0);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
@@ -480,6 +480,17 @@ Java_com_google_android_filament_View_nIsStencilBufferEnabled(JNIEnv *, jclass,
|
||||
return view->isStencilBufferEnabled();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetStereoscopicOptions(JNIEnv *, jclass, jlong nativeView,
|
||||
jboolean enabled) {
|
||||
View* view = (View*) nativeView;
|
||||
View::StereoscopicOptions options {
|
||||
.enabled = (bool) enabled
|
||||
};
|
||||
view->setStereoscopicOptions(options);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetGuardBandOptions(JNIEnv *, jclass,
|
||||
|
||||
@@ -111,6 +111,8 @@ public class Engine {
|
||||
|
||||
private long mNativeObject;
|
||||
|
||||
private Config mConfig;
|
||||
|
||||
@NonNull private final TransformManager mTransformManager;
|
||||
@NonNull private final LightManager mLightManager;
|
||||
@NonNull private final RenderableManager mRenderableManager;
|
||||
@@ -163,6 +165,7 @@ public class Engine {
|
||||
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
|
||||
private final BuilderFinalizer mFinalizer;
|
||||
private final long mNativeBuilder;
|
||||
private Config mConfig;
|
||||
|
||||
public Builder() {
|
||||
mNativeBuilder = nCreateBuilder();
|
||||
@@ -204,10 +207,12 @@ public class Engine {
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
public Builder config(Config config) {
|
||||
mConfig = config;
|
||||
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
|
||||
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
|
||||
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
|
||||
config.jobSystemThreadCount);
|
||||
config.jobSystemThreadCount, config.stereoscopicEyeCount,
|
||||
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -235,7 +240,7 @@ public class Engine {
|
||||
public Engine build() {
|
||||
long nativeEngine = nBuilderBuild(mNativeBuilder);
|
||||
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
|
||||
return new Engine(nativeEngine);
|
||||
return new Engine(nativeEngine, mConfig);
|
||||
}
|
||||
|
||||
private static class BuilderFinalizer {
|
||||
@@ -343,14 +348,36 @@ public class Engine {
|
||||
* the number of threads to use.
|
||||
*/
|
||||
public long jobSystemThreadCount = 0;
|
||||
|
||||
/**
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
* between 1 and Engine#getMaxStereoscopicEyes() (inclusive).
|
||||
*
|
||||
* @see View#setStereoscopicOptions
|
||||
* @see Engine#getMaxStereoscopicEyes
|
||||
*/
|
||||
public long stereoscopicEyeCount = 2;
|
||||
|
||||
/*
|
||||
* Size in MiB of the frame graph texture cache. This should be adjusted based on the
|
||||
* size of used render targets (typically the screen).
|
||||
*/
|
||||
public long resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
* This value determines for how many frames are texture entries kept in the cache.
|
||||
* The default value of 30 corresponds to about half a second at 60 fps.
|
||||
*/
|
||||
public long resourceAllocatorCacheMaxAge = 30;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine) {
|
||||
private Engine(long nativeEngine, Config config) {
|
||||
mNativeObject = nativeEngine;
|
||||
mTransformManager = new TransformManager(nGetTransformManager(nativeEngine));
|
||||
mLightManager = new LightManager(nGetLightManager(nativeEngine));
|
||||
mRenderableManager = new RenderableManager(nGetRenderableManager(nativeEngine));
|
||||
mEntityManager = new EntityManager(nGetEntityManager(nativeEngine));
|
||||
mConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,6 +570,37 @@ public class Engine {
|
||||
return nIsAutomaticInstancingEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configuration settings of this {@link Engine}.
|
||||
*
|
||||
* This method returns the configuration object that was supplied to the Engine's {@link
|
||||
* Builder#config} method during the creation of this Engine. If the {@link Builder::config}
|
||||
* method was not explicitly called (or called with null), this method returns the default
|
||||
* configuration settings.
|
||||
*
|
||||
* @return a {@link Config} object with this Engine's configuration
|
||||
* @see Builder#config
|
||||
*/
|
||||
@NonNull
|
||||
public Config getConfig() {
|
||||
if (mConfig == null) {
|
||||
mConfig = new Config();
|
||||
}
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
|
||||
* eyes rendered is set at Engine creation time with the {@link
|
||||
* Engine#Config#stereoscopicEyeCount} setting.
|
||||
*
|
||||
* @return the max number of stereoscopic eyes supported
|
||||
* @see Engine#Config#stereoscopicEyeCount
|
||||
*/
|
||||
public long getMaxStereoscopicEyes() {
|
||||
return nGetMaxStereoscopicEyes(getNativeObject());
|
||||
}
|
||||
|
||||
|
||||
// SwapChain
|
||||
|
||||
@@ -1171,6 +1229,7 @@ public class Engine {
|
||||
private static native long nGetEntityManager(long nativeEngine);
|
||||
private static native void nSetAutomaticInstancingEnabled(long nativeEngine, boolean enable);
|
||||
private static native boolean nIsAutomaticInstancingEnabled(long nativeEngine);
|
||||
private static native long nGetMaxStereoscopicEyes(long nativeEngine);
|
||||
private static native int nGetSupportedFeatureLevel(long nativeEngine);
|
||||
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
|
||||
private static native int nGetActiveFeatureLevel(long nativeEngine);
|
||||
@@ -1180,7 +1239,9 @@ public class Engine {
|
||||
private static native void nSetBuilderBackend(long nativeBuilder, long backend);
|
||||
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
|
||||
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
|
||||
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount);
|
||||
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
|
||||
long stereoscopicEyeCount,
|
||||
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge);
|
||||
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
|
||||
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
|
||||
private static native long nBuilderBuild(long nativeBuilder);
|
||||
|
||||
@@ -849,6 +849,10 @@ public class Texture {
|
||||
public static final int SAMPLEABLE = 0x10;
|
||||
/** Texture can be used as a subpass input */
|
||||
public static final int SUBPASS_INPUT = 0x20;
|
||||
/** Texture can be used the source of a blit() */
|
||||
public static final int BLIT_SRC = 0x40;
|
||||
/** Texture can be used the destination of a blit() */
|
||||
public static final int BLIT_DST = 0x80;
|
||||
/** by default textures are <code>UPLOADABLE</code> and <code>SAMPLEABLE</code>*/
|
||||
public static final int DEFAULT = UPLOADABLE | SAMPLEABLE;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ public class View {
|
||||
private AmbientOcclusionOptions mAmbientOcclusionOptions;
|
||||
private BloomOptions mBloomOptions;
|
||||
private FogOptions mFogOptions;
|
||||
private StereoscopicOptions mStereoscopicOptions;
|
||||
private RenderTarget mRenderTarget;
|
||||
private BlendMode mBlendMode;
|
||||
private DepthOfFieldOptions mDepthOfFieldOptions;
|
||||
@@ -1055,6 +1056,51 @@ public class View {
|
||||
return nIsStencilBufferEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stereoscopic rendering options for this view.
|
||||
*
|
||||
* <p>
|
||||
* Currently, only one type of stereoscopic rendering is supported: side-by-side.
|
||||
* Side-by-side stereo rendering splits the viewport into two halves: a left and right half.
|
||||
* Eye 0 will render to the left half, while Eye 1 will render into the right half.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Currently, the following features are not supported with stereoscopic rendering:
|
||||
* - post-processing
|
||||
* - shadowing
|
||||
* - punctual lights
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Stereo rendering depends on device and platform support. To check if stereo rendering is
|
||||
* supported, use {@link Engine#isStereoSupported()}. If stereo rendering is not supported, then
|
||||
* the stereoscopic options have no effect.
|
||||
* </p>
|
||||
*
|
||||
* @param options The stereoscopic options to use on this view
|
||||
* @see #getStereoscopicOptions
|
||||
*/
|
||||
public void setStereoscopicOptions(@NonNull StereoscopicOptions options) {
|
||||
mStereoscopicOptions = options;
|
||||
nSetStereoscopicOptions(getNativeObject(), options.enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stereoscopic options.
|
||||
*
|
||||
* @return options Stereoscopic options currently set.
|
||||
* @see #setStereoscopicOptions
|
||||
*/
|
||||
@NonNull
|
||||
public StereoscopicOptions getStereoscopicOptions() {
|
||||
if (mStereoscopicOptions == null) {
|
||||
mStereoscopicOptions = new StereoscopicOptions();
|
||||
}
|
||||
return mStereoscopicOptions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A class containing the result of a picking query
|
||||
*/
|
||||
@@ -1220,6 +1266,7 @@ public class View {
|
||||
private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, int levels, int blendMode, boolean threshold, boolean enabled, float highlight,
|
||||
boolean lensFlare, boolean starburst, float chromaticAberration, int ghostCount, float ghostSpacing, float ghostThreshold, float haloThickness, float haloRadius, float haloThreshold);
|
||||
private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float cutOffDistance, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, long skyColorNativeObject, boolean enabled);
|
||||
private static native void nSetStereoscopicOptions(long nativeView, boolean enabled);
|
||||
private static native void nSetBlendMode(long nativeView, int blendMode);
|
||||
private static native void nSetDepthOfFieldOptions(long nativeView, float cocScale, float maxApertureDiameter, boolean enabled, int filter,
|
||||
boolean nativeResolution, int foregroundRingCount, int backgroundRingCount, int fastGatherRingCount, int maxForegroundCOC, int maxBackgroundCOC);
|
||||
@@ -1590,6 +1637,10 @@ public class View {
|
||||
* circle of confusion scale factor (amount of blur)
|
||||
*/
|
||||
public float cocScale = 1.0f;
|
||||
/**
|
||||
* width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
|
||||
*/
|
||||
public float cocAspectRatio = 1.0f;
|
||||
/**
|
||||
* maximum aperture diameter in meters (zero to disable rotation)
|
||||
*/
|
||||
@@ -1805,7 +1856,7 @@ public class View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for Temporal Multi-Sample Anti-aliasing (MSAA)
|
||||
* Options for Multi-Sample Anti-aliasing (MSAA)
|
||||
* @see setMultiSampleAntiAliasingOptions()
|
||||
*/
|
||||
public static class MultiSampleAntiAliasingOptions {
|
||||
@@ -1829,21 +1880,111 @@ public class View {
|
||||
|
||||
/**
|
||||
* Options for Temporal Anti-aliasing (TAA)
|
||||
* Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process
|
||||
* shaders to be recompiled. These options should be changed or set during initialization.
|
||||
* `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time.
|
||||
*
|
||||
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
||||
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.
|
||||
*
|
||||
* @see setTemporalAntiAliasingOptions()
|
||||
*/
|
||||
public static class TemporalAntiAliasingOptions {
|
||||
public enum BoxType {
|
||||
/**
|
||||
* use an AABB neighborhood
|
||||
*/
|
||||
AABB,
|
||||
/**
|
||||
* use the variance of the neighborhood (not recommended)
|
||||
*/
|
||||
VARIANCE,
|
||||
/**
|
||||
* use both AABB and variance
|
||||
*/
|
||||
AABB_VARIANCE,
|
||||
}
|
||||
|
||||
public enum BoxClipping {
|
||||
/**
|
||||
* Accurate box clipping
|
||||
*/
|
||||
ACCURATE,
|
||||
/**
|
||||
* clamping
|
||||
*/
|
||||
CLAMP,
|
||||
/**
|
||||
* no rejections (use for debugging)
|
||||
*/
|
||||
NONE,
|
||||
}
|
||||
|
||||
public enum JitterPattern {
|
||||
RGSS_X4,
|
||||
UNIFORM_HELIX_X4,
|
||||
HALTON_23_X8,
|
||||
HALTON_23_X16,
|
||||
HALTON_23_X32,
|
||||
}
|
||||
|
||||
/**
|
||||
* reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother)
|
||||
* reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother)
|
||||
*/
|
||||
public float filterWidth = 1.0f;
|
||||
/**
|
||||
* history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
|
||||
*/
|
||||
public float feedback = 0.04f;
|
||||
public float feedback = 0.12f;
|
||||
/**
|
||||
* texturing lod bias (typically -1 or -2)
|
||||
*/
|
||||
public float lodBias = -1.0f;
|
||||
/**
|
||||
* post-TAA sharpen, especially useful when upscaling is true.
|
||||
*/
|
||||
public float sharpness = 0.0f;
|
||||
/**
|
||||
* enables or disables temporal anti-aliasing
|
||||
*/
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* 4x TAA upscaling. Disables Dynamic Resolution. [BETA]
|
||||
*/
|
||||
public boolean upscaling = false;
|
||||
/**
|
||||
* whether to filter the history buffer
|
||||
*/
|
||||
public boolean filterHistory = true;
|
||||
/**
|
||||
* whether to apply the reconstruction filter to the input
|
||||
*/
|
||||
public boolean filterInput = true;
|
||||
/**
|
||||
* whether to use the YcoCg color-space for history rejection
|
||||
*/
|
||||
public boolean useYCoCg = false;
|
||||
/**
|
||||
* type of color gamut box
|
||||
*/
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB;
|
||||
/**
|
||||
* clipping algorithm
|
||||
*/
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE;
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16;
|
||||
public float varianceGamma = 1.0f;
|
||||
/**
|
||||
* adjust the feedback dynamically to reduce flickering
|
||||
*/
|
||||
public boolean preventFlickering = false;
|
||||
/**
|
||||
* whether to apply history reprojection (debug option)
|
||||
*/
|
||||
public boolean historyReprojection = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+175
-166
@@ -182,28 +182,85 @@ public class UiHelper {
|
||||
void detach();
|
||||
}
|
||||
|
||||
private static class SurfaceViewHandler implements RenderSurface {
|
||||
private final SurfaceView mSurfaceView;
|
||||
private class SurfaceViewHandler implements RenderSurface, SurfaceHolder.Callback {
|
||||
@NonNull private final SurfaceView mSurfaceView;
|
||||
|
||||
SurfaceViewHandler(SurfaceView surface) {
|
||||
mSurfaceView = surface;
|
||||
SurfaceViewHandler(@NonNull SurfaceView surfaceView) {
|
||||
mSurfaceView = surfaceView;
|
||||
|
||||
@NonNull SurfaceHolder holder = surfaceView.getHolder();
|
||||
holder.addCallback(this);
|
||||
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceView's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
surfaceCreated(holder);
|
||||
// there is no way to retrieve the actual PixelFormat, since it is not used
|
||||
// in the callback, we can use whatever we want.
|
||||
surfaceChanged(holder, PixelFormat.RGBA_8888,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
mSurfaceView.getHolder().setFixedSize(width, height);
|
||||
@NonNull SurfaceHolder holder = mSurfaceView.getHolder();
|
||||
holder.setFixedSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
@NonNull SurfaceHolder holder = mSurfaceView.getHolder();
|
||||
holder.removeCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SurfaceHolderHandler implements RenderSurface {
|
||||
private class SurfaceHolderHandler implements RenderSurface, SurfaceHolder.Callback {
|
||||
private final SurfaceHolder mSurfaceHolder;
|
||||
|
||||
SurfaceHolderHandler(SurfaceHolder surface) {
|
||||
mSurfaceHolder = surface;
|
||||
SurfaceHolderHandler(@NonNull SurfaceHolder holder) {
|
||||
mSurfaceHolder = holder;
|
||||
holder.addCallback(this);
|
||||
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceHolder's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
surfaceCreated(holder);
|
||||
// there is no way to retrieve the actual PixelFormat, since it is not used
|
||||
// in the callback, we can use whatever we want.
|
||||
surfaceChanged(holder, PixelFormat.RGBA_8888,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,19 +270,55 @@ public class UiHelper {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
mSurfaceHolder.removeCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
private class TextureViewHandler implements RenderSurface {
|
||||
private class TextureViewHandler implements RenderSurface, TextureView.SurfaceTextureListener {
|
||||
private final TextureView mTextureView;
|
||||
private Surface mSurface;
|
||||
|
||||
TextureViewHandler(TextureView surface) { mTextureView = surface; }
|
||||
TextureViewHandler(@NonNull TextureView view) {
|
||||
mTextureView = view;
|
||||
mTextureView.setSurfaceTextureListener(this);
|
||||
// in case the View's SurfaceTexture already existed
|
||||
if (view.isAvailable()) {
|
||||
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
this.onSurfaceTextureAvailable(surfaceTexture,
|
||||
mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
surfaceTexture.setDefaultBufferSize(width, height);
|
||||
}
|
||||
}
|
||||
if (mRenderCallback != null) {
|
||||
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
|
||||
@@ -235,10 +328,69 @@ public class UiHelper {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
setSurface(null);
|
||||
mTextureView.setSurfaceTextureListener(null);
|
||||
}
|
||||
|
||||
void setSurface(Surface surface) {
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
final Surface surface = new Surface(surfaceTexture);
|
||||
setSurface(surface);
|
||||
createSwapChain(surface);
|
||||
|
||||
if (mRenderCallback != null) {
|
||||
// Call this the first time because onSurfaceTextureSizeChanged()
|
||||
// isn't called at initialization time
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
|
||||
if (mRenderCallback != null) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
|
||||
} else {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
// We must recreate the SwapChain to guarantee that it sees the new size.
|
||||
// More precisely, for an EGL client, the EGLSurface must be recreated. For
|
||||
// a Vulkan client, the SwapChain must be recreated. Calling
|
||||
// onNativeWindowChanged() will accomplish that.
|
||||
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
|
||||
// documentation.
|
||||
final Surface surface = getSurface();
|
||||
if (surface != null) {
|
||||
mRenderCallback.onNativeWindowChanged(surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
setSurface(null);
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { }
|
||||
|
||||
|
||||
private void setSurface(@Nullable Surface surface) {
|
||||
if (surface == null) {
|
||||
if (mSurface != null) {
|
||||
mSurface.release();
|
||||
@@ -247,7 +399,7 @@ public class UiHelper {
|
||||
mSurface = surface;
|
||||
}
|
||||
|
||||
public Surface getSurface() {
|
||||
private Surface getSurface() {
|
||||
return mSurface;
|
||||
}
|
||||
}
|
||||
@@ -293,6 +445,9 @@ public class UiHelper {
|
||||
* {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}.
|
||||
*/
|
||||
public void detach() {
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
}
|
||||
destroySwapChain();
|
||||
mNativeWindow = null;
|
||||
mRenderSurface = null;
|
||||
@@ -402,48 +557,8 @@ public class UiHelper {
|
||||
view.setZOrderOnTop(translucent);
|
||||
}
|
||||
|
||||
int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
|
||||
view.getHolder().setFormat(format);
|
||||
|
||||
view.getHolder().setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT);
|
||||
mRenderSurface = new SurfaceViewHandler(view);
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
};
|
||||
|
||||
SurfaceHolder holder = view.getHolder();
|
||||
holder.addCallback(callback);
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceView's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
callback.surfaceCreated(holder);
|
||||
callback.surfaceChanged(holder, format,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,76 +570,7 @@ public class UiHelper {
|
||||
public void attachTo(@NonNull TextureView view) {
|
||||
if (attach(view)) {
|
||||
view.setOpaque(isOpaque());
|
||||
|
||||
mRenderSurface = new TextureViewHandler(view);
|
||||
|
||||
TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
Surface surface = new Surface(surfaceTexture);
|
||||
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
|
||||
textureViewHandler.setSurface(surface);
|
||||
|
||||
createSwapChain(surface);
|
||||
|
||||
if (mRenderCallback != null) {
|
||||
// Call this the first time because onSurfaceTextureSizeChanged()
|
||||
// isn't called at initialization time
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
|
||||
if (mRenderCallback != null) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
|
||||
} else {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
// We must recreate the SwapChain to guarantee that it sees the new size.
|
||||
// More precisely, for an EGL client, the EGLSurface must be recreated. For
|
||||
// a Vulkan client, the SwapChain must be recreated. Calling
|
||||
// onNativeWindowChanged() will accomplish that.
|
||||
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
|
||||
// documentation.
|
||||
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
|
||||
mRenderCallback.onNativeWindowChanged(textureViewHandler.getSurface());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { }
|
||||
};
|
||||
|
||||
view.setSurfaceTextureListener(listener);
|
||||
|
||||
// in case the View's SurfaceTexture already existed
|
||||
if (view.isAvailable()) {
|
||||
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,46 +581,8 @@ public class UiHelper {
|
||||
*/
|
||||
public void attachTo(@NonNull SurfaceHolder holder) {
|
||||
if (attach(holder)) {
|
||||
int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
|
||||
holder.setFormat(format);
|
||||
|
||||
holder.setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT);
|
||||
mRenderSurface = new SurfaceHolderHandler(holder);
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
};
|
||||
|
||||
holder.addCallback(callback);
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceHolder's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
callback.surfaceCreated(holder);
|
||||
callback.surfaceChanged(holder, format,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,6 +593,10 @@ public class UiHelper {
|
||||
// nothing to do
|
||||
return false;
|
||||
}
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
mRenderSurface = null;
|
||||
}
|
||||
destroySwapChain();
|
||||
}
|
||||
mNativeWindow = nativeWindow;
|
||||
@@ -599,9 +611,6 @@ public class UiHelper {
|
||||
}
|
||||
|
||||
private void destroySwapChain() {
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
}
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onDetachedFromSurface();
|
||||
}
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ public class FilamentInstance {
|
||||
*
|
||||
* Ignored if variantIndex is out of bounds.
|
||||
*/
|
||||
void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
|
||||
public void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
|
||||
nApplyMaterialVariant(mNativeObject, variantIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.47.0
|
||||
VERSION_NAME=1.50.2
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Wed Nov 17 10:40:18 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -50,7 +50,7 @@ function replace {
|
||||
FIND_STR="${1//\{\{VERSION\}\}/${VERSION_REGEX}}"
|
||||
REPLACE_STR="${1//\{\{VERSION\}\}/${NEW_VERSION}}"
|
||||
local FILE_NAME="$2"
|
||||
if [ IS_DARWIN ]; then
|
||||
if [ $IS_DARWIN == 1 ]; then
|
||||
sed -i '' -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
else
|
||||
sed -i -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
|
||||
@@ -125,7 +125,13 @@ if [[ "${has_universal}" == "true" ]]; then
|
||||
|
||||
arch_output="${OUTPUT_PATH%.a}_${arch}.a"
|
||||
arch_outputs+=("$arch_output")
|
||||
combine_static_libs "$arch_output" $(find "$(pwd)/${archs_temp_dir}/${arch}" -iname '*.a')
|
||||
|
||||
archives=()
|
||||
while IFS= read -r -d $'\0'; do
|
||||
archives+=("$REPLY")
|
||||
done < <(find "$(pwd)/${archs_temp_dir}/${arch}" -iname '*.a' -print0)
|
||||
|
||||
combine_static_libs "$arch_output" "$archives"
|
||||
done
|
||||
|
||||
# Finally, combine the single-architecture archives into a universal binary.
|
||||
|
||||
+21
-2
File diff suppressed because one or more lines are too long
Binary file not shown.
+14
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -587,6 +587,7 @@
|
||||
<li><em>overrides</em> Dictionary with one or more of the following properties: mapSize, shadowCascades, constantBias, normalBias, shadowFar, shadowNearHint, shadowFarHint, stable, polygonOffsetConstant, polygonOffsetSlope, \</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>engine.setStereoscopicOptions()</strong></li>
|
||||
<li><strong>engine.setTemporalAntiAliasingOptions()</strong></li>
|
||||
<li><strong>engine.setVignetteOptions()</strong></li>
|
||||
</ul>
|
||||
@@ -1462,6 +1463,8 @@ This defines the following functions:</p>
|
||||
<li>STENCIL_ATTACHMENT</li>
|
||||
<li>UPLOADABLE</li>
|
||||
<li>SAMPLEABLE</li>
|
||||
<li>BLIT_SRC</li>
|
||||
<li>BLIT_DST</li>
|
||||
<li>SUBPASS_INPUT</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+21
-19
@@ -209,10 +209,13 @@ set(PRIVATE_HDRS
|
||||
)
|
||||
|
||||
set(MATERIAL_SRCS
|
||||
src/materials/fsr/fsr_easu.mat
|
||||
src/materials/fsr/fsr_easu_mobile.mat
|
||||
src/materials/fsr/fsr_easu_mobileF.mat
|
||||
src/materials/fsr/fsr_rcas.mat
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
src/materials/bloom/bloomUpsample.mat
|
||||
src/materials/colorGrading/colorGrading.mat
|
||||
src/materials/colorGrading/colorGradingAsSubpass.mat
|
||||
src/materials/colorGrading/customResolveAsSubpass.mat
|
||||
@@ -220,29 +223,28 @@ set(MATERIAL_SRCS
|
||||
src/materials/defaultMaterial.mat
|
||||
src/materials/dof/dof.mat
|
||||
src/materials/dof/dofCoc.mat
|
||||
src/materials/dof/dofDownsample.mat
|
||||
src/materials/dof/dofCombine.mat
|
||||
src/materials/dof/dofDilate.mat
|
||||
src/materials/dof/dofDownsample.mat
|
||||
src/materials/dof/dofMedian.mat
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofTiles.mat
|
||||
src/materials/dof/dofTilesSwizzle.mat
|
||||
src/materials/dof/dofDilate.mat
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofMedian.mat
|
||||
src/materials/flare/flare.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
src/materials/bloom/bloomUpsample.mat
|
||||
src/materials/fsr/fsr_easu.mat
|
||||
src/materials/fsr/fsr_easu_mobile.mat
|
||||
src/materials/fsr/fsr_easu_mobileF.mat
|
||||
src/materials/fsr/fsr_rcas.mat
|
||||
src/materials/resolveDepth.mat
|
||||
src/materials/separableGaussianBlur.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/shadowmap.mat
|
||||
src/materials/ssao/bilateralBlur.mat
|
||||
src/materials/ssao/bilateralBlurBentNormals.mat
|
||||
src/materials/ssao/mipmapDepth.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/ssao/sao.mat
|
||||
src/materials/ssao/saoBentNormals.mat
|
||||
src/materials/separableGaussianBlur.mat
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/vsmMipmap.mat
|
||||
src/materials/blitLow.mat
|
||||
)
|
||||
|
||||
set(MATERIAL_FL0_SRCS
|
||||
@@ -320,7 +322,7 @@ foreach (mat_src ${MATERIAL_SRCS})
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach()
|
||||
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0 AND FILAMENT_SUPPORTS_OPENGL)
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
foreach (mat_src ${MATERIAL_FL0_SRCS})
|
||||
get_filename_component(localname "${mat_src}" NAME_WE)
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
@@ -328,7 +330,7 @@ if (FILAMENT_ENABLE_FEATURE_LEVEL_0 AND FILAMENT_SUPPORTS_OPENGL)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path}
|
||||
COMMAND matc -a opengl -p ${MATC_TARGET} ${MATC_OPT_FLAGS} -o ${output_path} ${fullname}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
|
||||
@@ -169,6 +169,8 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
src/vulkan/spirv/VulkanSpirvUtils.cpp
|
||||
src/vulkan/spirv/VulkanSpirvUtils.h
|
||||
src/vulkan/VulkanBlitter.cpp
|
||||
src/vulkan/VulkanBlitter.h
|
||||
src/vulkan/VulkanBuffer.cpp
|
||||
@@ -313,6 +315,7 @@ endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc vkshaders smol-v)
|
||||
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_METAL)
|
||||
@@ -397,8 +400,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include)
|
||||
# ==================================================================================================
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
|
||||
if (APPLE)
|
||||
add_library(backend_test STATIC
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
@@ -410,19 +413,26 @@ if (APPLE)
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_RenderExternalImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(backend_test PRIVATE
|
||||
)
|
||||
set(BACKEND_TEST_LIBS
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_RenderExternalImage.cpp)
|
||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test PRIVATE ${BACKEND_TEST_LIBS})
|
||||
|
||||
set(BACKEND_TEST_DEPS
|
||||
OGLCompiler
|
||||
@@ -436,12 +446,11 @@ if (APPLE)
|
||||
glslang
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-msl
|
||||
)
|
||||
spirv-cross-msl)
|
||||
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
|
||||
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
|
||||
@@ -455,15 +464,21 @@ if (APPLE)
|
||||
endif()
|
||||
|
||||
set_target_properties(backend_test PROPERTIES FOLDER Tests)
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# 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)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# 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)
|
||||
if (LINUX)
|
||||
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
|
||||
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_CALLBACKHANDLER_H
|
||||
#define TNT_FILAMENT_BACKEND_CALLBACKHANDLER_H
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <array> // FIXME: STL headers are not allowed in public headers
|
||||
#include <array> // FIXME: STL headers are not allowed in public headers
|
||||
#include <type_traits> // FIXME: STL headers are not allowed in public headers
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -227,6 +228,7 @@ struct Viewport {
|
||||
int32_t top() const noexcept { return bottom + int32_t(height); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the mapping of the near and far clipping plane to window coordinates.
|
||||
*/
|
||||
@@ -658,13 +660,15 @@ enum class TextureFormat : uint16_t {
|
||||
|
||||
//! Bitmask describing the intended Texture Usage
|
||||
enum class TextureUsage : uint8_t {
|
||||
NONE = 0x0,
|
||||
COLOR_ATTACHMENT = 0x1, //!< Texture can be used as a color attachment
|
||||
DEPTH_ATTACHMENT = 0x2, //!< Texture can be used as a depth attachment
|
||||
STENCIL_ATTACHMENT = 0x4, //!< Texture can be used as a stencil attachment
|
||||
UPLOADABLE = 0x8, //!< Data can be uploaded into this texture (default)
|
||||
NONE = 0x00,
|
||||
COLOR_ATTACHMENT = 0x01, //!< Texture can be used as a color attachment
|
||||
DEPTH_ATTACHMENT = 0x02, //!< Texture can be used as a depth attachment
|
||||
STENCIL_ATTACHMENT = 0x04, //!< Texture can be used as a stencil attachment
|
||||
UPLOADABLE = 0x08, //!< Data can be uploaded into this texture (default)
|
||||
SAMPLEABLE = 0x10, //!< Texture can be sampled (default)
|
||||
SUBPASS_INPUT = 0x20, //!< Texture can be used as a subpass input
|
||||
BLIT_SRC = 0x40, //!< Texture can be used the source of a blit()
|
||||
BLIT_DST = 0x80, //!< Texture can be used the destination of a blit()
|
||||
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
|
||||
};
|
||||
|
||||
@@ -692,6 +696,17 @@ static constexpr bool isDepthFormat(TextureFormat format) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr bool isStencilFormat(TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::STENCIL8:
|
||||
case TextureFormat::DEPTH24_STENCIL8:
|
||||
case TextureFormat::DEPTH32F_STENCIL8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr bool isUnsignedIntFormat(TextureFormat format) {
|
||||
switch (format) {
|
||||
case TextureFormat::R8UI:
|
||||
@@ -1180,8 +1195,8 @@ using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
||||
enum class Workaround : uint16_t {
|
||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||
SPLIT_EASU,
|
||||
// Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're read-only for
|
||||
// the whole render pass.
|
||||
// Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're
|
||||
// read-only for the whole render pass.
|
||||
ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP,
|
||||
// for some uniform arrays, it's needed to do an initialization to avoid crash on adreno gpu
|
||||
ADRENO_UNIFORM_ARRAY_CRASH,
|
||||
|
||||
@@ -17,16 +17,14 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_HANDLE_H
|
||||
#define TNT_FILAMENT_BACKEND_HANDLE_H
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#if !defined(NDEBUG)
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
#endif
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <type_traits> // FIXME: STL headers are not allowed in public headers
|
||||
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -54,7 +52,7 @@ struct HwVertexBuffer;
|
||||
class HandleBase {
|
||||
public:
|
||||
using HandleId = uint32_t;
|
||||
static constexpr const HandleId nullid = HandleId{ std::numeric_limits<HandleId>::max() };
|
||||
static constexpr const HandleId nullid = HandleId{ UINT32_MAX };
|
||||
|
||||
constexpr HandleBase() noexcept: object(nullid) {}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <limits>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -33,10 +33,7 @@ struct PipelineState {
|
||||
RasterState rasterState;
|
||||
StencilState stencilState;
|
||||
PolygonOffset polygonOffset;
|
||||
Viewport scissor{ 0, 0,
|
||||
(uint32_t)std::numeric_limits<int32_t>::max(),
|
||||
(uint32_t)std::numeric_limits<int32_t>::max()
|
||||
};
|
||||
Viewport scissor{ 0, 0, (uint32_t)INT32_MAX, (uint32_t)INT32_MAX };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORM_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Invocable.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class Driver;
|
||||
@@ -41,18 +41,24 @@ public:
|
||||
struct Stream {};
|
||||
|
||||
struct DriverConfig {
|
||||
/*
|
||||
* size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
||||
/**
|
||||
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
||||
* Driver clamps to valid values.
|
||||
*/
|
||||
size_t handleArenaSize = 0;
|
||||
|
||||
/*
|
||||
* this number of most-recently destroyed textures will be tracked for use-after-free.
|
||||
/**
|
||||
* This number of most-recently destroyed textures will be tracked for use-after-free.
|
||||
* Throws an exception when a texture is freed but still bound to a SamplerGroup and used in
|
||||
* a draw call. 0 disables completely. Currently only respected by the Metal backend.
|
||||
*/
|
||||
size_t textureUseAfterFreePoolSize = 0;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL backend.
|
||||
*/
|
||||
bool disableParallelShaderCompile = false;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
@@ -78,7 +84,7 @@ public:
|
||||
*
|
||||
* @return nullptr on failure, or a pointer to the newly created driver.
|
||||
*/
|
||||
virtual backend::Driver* createDriver(void* sharedContext,
|
||||
virtual backend::Driver* UTILS_NULLABLE createDriver(void* UTILS_NULLABLE sharedContext,
|
||||
const DriverConfig& driverConfig) noexcept = 0;
|
||||
|
||||
/**
|
||||
@@ -96,7 +102,8 @@ public:
|
||||
* cache.
|
||||
*/
|
||||
using InsertBlobFunc = utils::Invocable<
|
||||
void(const void* key, size_t keySize, const void* value, size_t valueSize)>;
|
||||
void(const void* UTILS_NONNULL key, size_t keySize,
|
||||
const void* UTILS_NONNULL value, size_t valueSize)>;
|
||||
|
||||
/*
|
||||
* RetrieveBlobFunc is an Invocable to an application-provided function that a
|
||||
@@ -104,7 +111,8 @@ public:
|
||||
* cache.
|
||||
*/
|
||||
using RetrieveBlobFunc = utils::Invocable<
|
||||
size_t(const void* key, size_t keySize, void* value, size_t valueSize)>;
|
||||
size_t(const void* UTILS_NONNULL key, size_t keySize,
|
||||
void* UTILS_NONNULL value, size_t valueSize)>;
|
||||
|
||||
/**
|
||||
* Sets the callback functions that the backend can use to interact with caching functionality
|
||||
@@ -157,7 +165,8 @@ public:
|
||||
* @param value pointer to the beginning of the value data that is to be inserted
|
||||
* @param valueSize specifies the size in byte of the data pointed to by <value>
|
||||
*/
|
||||
void insertBlob(const void* key, size_t keySize, const void* value, size_t valueSize);
|
||||
void insertBlob(const void* UTILS_NONNULL key, size_t keySize,
|
||||
const void* UTILS_NONNULL value, size_t valueSize);
|
||||
|
||||
/**
|
||||
* To retrieve the binary value associated with a given key from the cache, a
|
||||
@@ -176,7 +185,8 @@ public:
|
||||
* @return If the cache contains a value associated with the given key then the
|
||||
* size of that binary value in bytes is returned. Otherwise 0 is returned.
|
||||
*/
|
||||
size_t retrieveBlob(const void* key, size_t keySize, void* value, size_t valueSize);
|
||||
size_t retrieveBlob(const void* UTILS_NONNULL key, size_t keySize,
|
||||
void* UTILS_NONNULL value, size_t valueSize);
|
||||
|
||||
private:
|
||||
InsertBlobFunc mInsertBlob;
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A PresentCallable is a callable object that, when called, schedules a frame for presentation on
|
||||
@@ -98,7 +97,6 @@ private:
|
||||
*/
|
||||
using FrameFinishedCallback UTILS_DEPRECATED = void(*)(PresentCallable callable, void* user);
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PRESENTCALLABLE
|
||||
|
||||
@@ -17,17 +17,19 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PRIVATE_PROGRAM_H
|
||||
#define TNT_FILAMENT_BACKEND_PRIVATE_PROGRAM_H
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <array>
|
||||
#include <variant>
|
||||
#include <array> // FIXME: STL headers are not allowed in public headers
|
||||
#include <utility> // 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>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct UTILS_PUBLIC SamplerDescriptor {
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_TARGETBUFFERINFO_H
|
||||
#define TNT_FILAMENT_BACKEND_TARGETBUFFERINFO_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -18,8 +18,13 @@
|
||||
#define TNT_FILAMENT_BACKEND_PRIVATE_OPENGLPLATFORM_H
|
||||
|
||||
#include <backend/AcquiredImage.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class Driver;
|
||||
@@ -38,8 +43,8 @@ protected:
|
||||
* Derived classes can use this to instantiate the default OpenGLDriver backend.
|
||||
* This is typically called from your implementation of createDriver()
|
||||
*/
|
||||
static Driver* createDefaultDriver(OpenGLPlatform* platform,
|
||||
void* sharedContext, const DriverConfig& driverConfig);
|
||||
static Driver* UTILS_NULLABLE createDefaultDriver(OpenGLPlatform* UTILS_NONNULL platform,
|
||||
void* UTILS_NULLABLE sharedContext, const DriverConfig& driverConfig);
|
||||
|
||||
~OpenGLPlatform() noexcept override;
|
||||
|
||||
@@ -66,7 +71,8 @@ public:
|
||||
* @return The driver's SwapChain object.
|
||||
*
|
||||
*/
|
||||
virtual SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) noexcept = 0;
|
||||
virtual SwapChain* UTILS_NONNULL createSwapChain(
|
||||
void* UTILS_NULLABLE nativeWindow, uint64_t flags) noexcept = 0;
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag.
|
||||
@@ -87,13 +93,14 @@ public:
|
||||
* TODO: we need a more generic way of passing construction parameters
|
||||
* A void* might be enough.
|
||||
*/
|
||||
virtual SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept = 0;
|
||||
virtual SwapChain* UTILS_NULLABLE createSwapChain(
|
||||
uint32_t width, uint32_t height, uint64_t flags) noexcept = 0;
|
||||
|
||||
/**
|
||||
* Called by the driver to destroys the SwapChain
|
||||
* @param swapChain SwapChain to be destroyed.
|
||||
*/
|
||||
virtual void destroySwapChain(SwapChain* swapChain) noexcept = 0;
|
||||
virtual void destroySwapChain(SwapChain* UTILS_NONNULL swapChain) noexcept = 0;
|
||||
|
||||
/**
|
||||
* Returns the set of buffers that must be preserved up to the call to commit().
|
||||
@@ -106,7 +113,7 @@ public:
|
||||
* @return buffer that must be preserved
|
||||
* @see commit()
|
||||
*/
|
||||
virtual TargetBufferFlags getPreservedFlags(SwapChain* swapChain) noexcept;
|
||||
virtual TargetBufferFlags getPreservedFlags(SwapChain* UTILS_NONNULL swapChain) noexcept;
|
||||
|
||||
/**
|
||||
* Called by the driver to establish the default FBO. The default implementation returns 0.
|
||||
@@ -120,14 +127,16 @@ public:
|
||||
* @param drawSwapChain SwapChain to draw to. It must be bound to the default FBO.
|
||||
* @param readSwapChain SwapChain to read from (for operation like `glBlitFramebuffer`)
|
||||
*/
|
||||
virtual void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept = 0;
|
||||
virtual void makeCurrent(
|
||||
SwapChain* UTILS_NONNULL drawSwapChain,
|
||||
SwapChain* UTILS_NONNULL readSwapChain) noexcept = 0;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param swapChain the SwapChain to present.
|
||||
*/
|
||||
virtual void commit(SwapChain* swapChain) noexcept = 0;
|
||||
virtual void commit(SwapChain* UTILS_NONNULL swapChain) noexcept = 0;
|
||||
|
||||
/**
|
||||
* Set the time the next committed buffer should be presented to the user at.
|
||||
@@ -152,14 +161,14 @@ public:
|
||||
*
|
||||
* @return A Fence object. The default implementation returns nullptr.
|
||||
*/
|
||||
virtual Fence* createFence() noexcept;
|
||||
virtual Fence* UTILS_NULLABLE createFence() noexcept;
|
||||
|
||||
/**
|
||||
* Destroys a Fence object. The default implementation does nothing.
|
||||
*
|
||||
* @param fence Fence to destroy.
|
||||
*/
|
||||
virtual void destroyFence(Fence* fence) noexcept;
|
||||
virtual void destroyFence(Fence* UTILS_NONNULL fence) noexcept;
|
||||
|
||||
/**
|
||||
* Waits on a Fence.
|
||||
@@ -169,7 +178,7 @@ public:
|
||||
* @return Whether the fence signaled or timed out. See backend::FenceStatus.
|
||||
* The default implementation always return backend::FenceStatus::ERROR.
|
||||
*/
|
||||
virtual backend::FenceStatus waitFence(Fence* fence, uint64_t timeout) noexcept;
|
||||
virtual backend::FenceStatus waitFence(Fence* UTILS_NONNULL fence, uint64_t timeout) noexcept;
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
@@ -183,13 +192,13 @@ public:
|
||||
* @param nativeStream The native stream, this parameter depends on the concrete implementation.
|
||||
* @return A new Stream object.
|
||||
*/
|
||||
virtual Stream* createStream(void* nativeStream) noexcept;
|
||||
virtual Stream* UTILS_NULLABLE createStream(void* UTILS_NULLABLE nativeStream) noexcept;
|
||||
|
||||
/**
|
||||
* Destroys a Stream.
|
||||
* @param stream Stream to destroy.
|
||||
*/
|
||||
virtual void destroyStream(Stream* stream) noexcept;
|
||||
virtual void destroyStream(Stream* UTILS_NONNULL stream) noexcept;
|
||||
|
||||
/**
|
||||
* The specified stream takes ownership of the texture (tname) object
|
||||
@@ -199,20 +208,21 @@ public:
|
||||
* @param stream Stream to take ownership of the texture
|
||||
* @param tname GL texture id to "bind" to the Stream.
|
||||
*/
|
||||
virtual void attach(Stream* stream, intptr_t tname) noexcept;
|
||||
virtual void attach(Stream* UTILS_NONNULL stream, intptr_t tname) noexcept;
|
||||
|
||||
/**
|
||||
* Destroys the texture associated to the stream
|
||||
* @param stream Stream to detach from its texture
|
||||
*/
|
||||
virtual void detach(Stream* stream) noexcept;
|
||||
virtual void detach(Stream* UTILS_NONNULL stream) noexcept;
|
||||
|
||||
/**
|
||||
* Updates the content of the texture attached to the stream.
|
||||
* @param stream Stream to update
|
||||
* @param timestamp Output parameter: Timestamp of the image bound to the texture.
|
||||
*/
|
||||
virtual void updateTexImage(Stream* stream, int64_t* timestamp) noexcept;
|
||||
virtual void updateTexImage(Stream* UTILS_NONNULL stream,
|
||||
int64_t* UTILS_NONNULL timestamp) noexcept;
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
@@ -225,13 +235,13 @@ public:
|
||||
* implementation could just return { 0, GL_TEXTURE_2D } at this point. The actual
|
||||
* values can be delayed until setExternalImage.
|
||||
*/
|
||||
virtual ExternalTexture *createExternalImageTexture() noexcept;
|
||||
virtual ExternalTexture* UTILS_NULLABLE createExternalImageTexture() noexcept;
|
||||
|
||||
/**
|
||||
* Destroys an external texture handle and associated data.
|
||||
* @param texture a pointer to the handle to destroy.
|
||||
*/
|
||||
virtual void destroyExternalImage(ExternalTexture* texture) noexcept;
|
||||
virtual void destroyExternalImage(ExternalTexture* UTILS_NONNULL texture) noexcept;
|
||||
|
||||
// called on the application thread to allow Filament to take ownership of the image
|
||||
|
||||
@@ -244,7 +254,7 @@ public:
|
||||
* @param externalImage A token representing the platform's external image.
|
||||
* @see destroyExternalImage
|
||||
*/
|
||||
virtual void retainExternalImage(void* externalImage) noexcept;
|
||||
virtual void retainExternalImage(void* UTILS_NONNULL externalImage) noexcept;
|
||||
|
||||
/**
|
||||
* Called to bind the platform-specific externalImage to an ExternalTexture.
|
||||
@@ -258,7 +268,8 @@ public:
|
||||
* @param texture an in/out pointer to ExternalTexture, id and target can be updated if necessary.
|
||||
* @return true on success, false on error.
|
||||
*/
|
||||
virtual bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept;
|
||||
virtual bool setExternalImage(void* UTILS_NONNULL externalImage,
|
||||
ExternalTexture* UTILS_NONNULL texture) noexcept;
|
||||
|
||||
/**
|
||||
* The method allows platforms to convert a user-supplied external image object into a new type
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct PlatformCocoaTouchGLImpl;
|
||||
class PlatformCocoaTouchGL : public OpenGLPlatform {
|
||||
public:
|
||||
PlatformCocoaTouchGL();
|
||||
~PlatformCocoaTouchGL() noexcept;
|
||||
~PlatformCocoaTouchGL() noexcept override;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Platform Interface
|
||||
|
||||
@@ -17,18 +17,21 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <EGL/eglplatform.h>
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,8 +17,14 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
|
||||
|
||||
#include <backend/AcquiredImage.h>
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class ExternalStreamManagerAndroid;
|
||||
|
||||
@@ -20,13 +20,18 @@
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using SwapChain = Platform::SwapChain;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PRIVATE_CIRCULARBUFFER_H
|
||||
#define TNT_FILAMENT_BACKEND_PRIVATE_CIRCULARBUFFER_H
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -40,28 +40,36 @@ public:
|
||||
|
||||
~CircularBuffer() noexcept;
|
||||
|
||||
// allocates 'size' bytes in the circular buffer and returns a pointer to the memory
|
||||
// return the current head and moves it forward by size bytes
|
||||
inline void* allocate(size_t size) noexcept {
|
||||
static size_t getBlockSize() noexcept { return sPageSize; }
|
||||
|
||||
// Total size of circular buffer. This is a constant.
|
||||
size_t size() const noexcept { return mSize; }
|
||||
|
||||
// Allocates `s` bytes in the circular buffer and returns a pointer to the memory. All
|
||||
// allocations must not exceed size() bytes.
|
||||
inline void* allocate(size_t s) noexcept {
|
||||
// We can never allocate more that size().
|
||||
assert_invariant(getUsed() + s <= size());
|
||||
char* const cur = static_cast<char*>(mHead);
|
||||
mHead = cur + size;
|
||||
mHead = cur + s;
|
||||
return cur;
|
||||
}
|
||||
|
||||
// Total size of circular buffer
|
||||
size_t size() const noexcept { return mSize; }
|
||||
|
||||
// returns true if the buffer is empty (e.g. after calling flush)
|
||||
// Returns true if the buffer is empty, i.e.: no allocations were made since
|
||||
// calling getBuffer();
|
||||
bool empty() const noexcept { return mTail == mHead; }
|
||||
|
||||
void* getHead() const noexcept { return mHead; }
|
||||
// Returns the size used since the last call to getBuffer()
|
||||
size_t getUsed() const noexcept { return intptr_t(mHead) - intptr_t(mTail); }
|
||||
|
||||
void* getTail() const noexcept { return mTail; }
|
||||
|
||||
// call at least once every getRequiredSize() bytes allocated from the buffer
|
||||
void circularize() noexcept;
|
||||
|
||||
static size_t getBlockSize() noexcept { return sPageSize; }
|
||||
// Retrieves the current allocated range and frees it. It is the responsibility of the caller
|
||||
// to make sure the returned range is no longer in use by the time allocate() allocates
|
||||
// (size() - getUsed()) bytes.
|
||||
struct Range {
|
||||
void* tail;
|
||||
void* head;
|
||||
};
|
||||
Range getBuffer() noexcept;
|
||||
|
||||
private:
|
||||
void* alloc(size_t size) noexcept;
|
||||
@@ -69,10 +77,10 @@ private:
|
||||
|
||||
// pointer to the beginning of the circular buffer (constant)
|
||||
void* mData = nullptr;
|
||||
int mUsesAshmem = -1;
|
||||
int mAshmemFd = -1;
|
||||
|
||||
// size of the circular buffer (constant)
|
||||
size_t mSize = 0;
|
||||
size_t const mSize;
|
||||
|
||||
// pointer to the beginning of recorded data
|
||||
void* mTail = nullptr;
|
||||
|
||||
@@ -19,19 +19,21 @@
|
||||
|
||||
#include "private/backend/CircularBuffer.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Condition.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/*
|
||||
* A producer-consumer command queue that uses a CircularBuffer as main storage
|
||||
*/
|
||||
class CommandBufferQueue {
|
||||
struct Slice {
|
||||
struct Range {
|
||||
void* begin;
|
||||
void* end;
|
||||
};
|
||||
@@ -44,7 +46,7 @@ class CommandBufferQueue {
|
||||
|
||||
mutable utils::Mutex mLock;
|
||||
mutable utils::Condition mCondition;
|
||||
mutable std::vector<Slice> mCommandBuffersToExecute;
|
||||
mutable std::vector<Range> mCommandBuffersToExecute;
|
||||
size_t mFreeSpace = 0;
|
||||
size_t mHighWatermark = 0;
|
||||
uint32_t mExitRequested = 0;
|
||||
@@ -56,17 +58,20 @@ public:
|
||||
CommandBufferQueue(size_t requiredSize, size_t bufferSize);
|
||||
~CommandBufferQueue();
|
||||
|
||||
CircularBuffer& getCircularBuffer() { return mCircularBuffer; }
|
||||
CircularBuffer& getCircularBuffer() noexcept { return mCircularBuffer; }
|
||||
CircularBuffer const& getCircularBuffer() const noexcept { return mCircularBuffer; }
|
||||
|
||||
size_t getCapacity() const noexcept { return mRequiredSize; }
|
||||
|
||||
size_t getHighWatermark() const noexcept { return mHighWatermark; }
|
||||
|
||||
// wait for commands to be available and returns an array containing these commands
|
||||
std::vector<Slice> waitForCommands() const;
|
||||
std::vector<Range> waitForCommands() const;
|
||||
|
||||
// return the memory used by this command buffer to the circular buffer
|
||||
// WARNING: releaseBuffer() must be called in sequence of the Slices returned by
|
||||
// waitForCommands()
|
||||
void releaseBuffer(Slice const& buffer);
|
||||
void releaseBuffer(Range const& buffer);
|
||||
|
||||
// all commands buffers (Slices) written to this point are returned by waitForCommand(). This
|
||||
// call blocks until the CircularBuffer has at least mRequiredSize bytes available.
|
||||
|
||||
@@ -32,11 +32,13 @@
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ThreadUtils.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -152,14 +154,14 @@ struct CommandType<void (Driver::*)(ARGS...)> {
|
||||
}
|
||||
|
||||
// placement new declared as "throw" to avoid the compiler's null-check
|
||||
inline void* operator new(std::size_t size, void* ptr) {
|
||||
inline void* operator new(std::size_t, void* ptr) {
|
||||
assert_invariant(ptr);
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// convert an method of "class Driver" into a Command<> type
|
||||
// convert a method of "class Driver" into a Command<> type
|
||||
#define COMMAND_TYPE(method) CommandType<decltype(&Driver::method)>::Command<&Driver::method>
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -211,6 +213,8 @@ public:
|
||||
CommandStream(CommandStream const& rhs) noexcept = delete;
|
||||
CommandStream& operator=(CommandStream const& rhs) noexcept = delete;
|
||||
|
||||
CircularBuffer const& getCircularBuffer() const noexcept { return mCurrentBuffer; }
|
||||
|
||||
public:
|
||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
||||
inline void methodName(paramsDecl) { \
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Command debugging off. debugging virtuals are not called.
|
||||
|
||||
@@ -300,10 +300,10 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, canGenerateMipmaps)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
|
||||
@@ -468,7 +468,7 @@ DECL_DRIVER_API_N(readBufferSubData,
|
||||
* --------------------
|
||||
*/
|
||||
|
||||
DECL_DRIVER_API_N(blit,
|
||||
DECL_DRIVER_API_N(blitDEPRECATED,
|
||||
backend::TargetBufferFlags, buffers,
|
||||
backend::RenderTargetHandle, dst,
|
||||
backend::Viewport, dstRect,
|
||||
@@ -476,6 +476,21 @@ DECL_DRIVER_API_N(blit,
|
||||
backend::Viewport, srcRect,
|
||||
backend::SamplerMagFilter, filter)
|
||||
|
||||
DECL_DRIVER_API_N(resolve,
|
||||
backend::TextureHandle, dst,
|
||||
uint8_t, dstLevel, uint8_t, dstLayer,
|
||||
backend::TextureHandle, src,
|
||||
uint8_t, srcLevel, uint8_t, srcLayer)
|
||||
|
||||
DECL_DRIVER_API_N(blit,
|
||||
backend::TextureHandle, dst,
|
||||
uint8_t, dstLevel, uint8_t, dstLayer,
|
||||
math::uint2, dstOrigin,
|
||||
backend::TextureHandle, src,
|
||||
uint8_t, srcLevel, uint8_t, srcLayer,
|
||||
math::uint2, srcOrigin,
|
||||
math::uint2, size)
|
||||
|
||||
DECL_DRIVER_API_N(draw,
|
||||
backend::PipelineState, state,
|
||||
backend::RenderPrimitiveHandle, rph,
|
||||
|
||||
@@ -22,9 +22,18 @@
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <exception>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if !defined(NDEBUG) && UTILS_HAS_RTTI
|
||||
# define HANDLE_TYPE_SAFETY 1
|
||||
#else
|
||||
@@ -222,7 +231,7 @@ private:
|
||||
explicit Allocator(const utils::AreaPolicy::HeapArea& area);
|
||||
|
||||
// this is in fact always called with a constexpr size argument
|
||||
[[nodiscard]] inline void* alloc(size_t size, size_t alignment, size_t extra) noexcept {
|
||||
[[nodiscard]] inline void* alloc(size_t size, size_t, size_t extra = 0) noexcept {
|
||||
void* p = nullptr;
|
||||
if (size <= mPool0.getSize()) p = mPool0.alloc(size, 16, extra);
|
||||
else if (size <= mPool1.getSize()) p = mPool1.alloc(size, 16, extra);
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_FACTORY_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORM_FACTORY_H
|
||||
|
||||
#include "backend/DriverEnums.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "utils/compiler.h"
|
||||
#include <utils/compiler.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -17,15 +17,13 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PRIVATE_SAMPLERGROUP_H
|
||||
#define TNT_FILAMENT_BACKEND_PRIVATE_SAMPLERGROUP_H
|
||||
|
||||
#include "backend/DriverApiForward.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/SamplerDescriptor.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
|
||||
#include "private/backend/CircularBuffer.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/architecture.h>
|
||||
#include <utils/ashmem.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#if !defined(WIN32) && !defined(__EMSCRIPTEN__) && !defined(IOS)
|
||||
# include <sys/mman.h>
|
||||
# include <unistd.h>
|
||||
@@ -24,23 +32,20 @@
|
||||
# define HAS_MMAP 0
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <utils/architecture.h>
|
||||
#include <utils/ashmem.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
size_t CircularBuffer::sPageSize = arch::getPageSize();
|
||||
|
||||
CircularBuffer::CircularBuffer(size_t size) {
|
||||
CircularBuffer::CircularBuffer(size_t size)
|
||||
: mSize(size) {
|
||||
mData = alloc(size);
|
||||
mSize = size;
|
||||
mTail = mData;
|
||||
mHead = mData;
|
||||
}
|
||||
@@ -85,7 +90,7 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
MAP_PRIVATE, fd, (off_t)size);
|
||||
if (vaddr_guard != MAP_FAILED && (vaddr_guard == (char*)vaddr_shadow + size)) {
|
||||
// woo-hoo success!
|
||||
mUsesAshmem = fd;
|
||||
mAshmemFd = fd;
|
||||
data = vaddr;
|
||||
}
|
||||
}
|
||||
@@ -93,7 +98,7 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(mUsesAshmem < 0)) {
|
||||
if (UTILS_UNLIKELY(mAshmemFd < 0)) {
|
||||
// ashmem failed
|
||||
if (vaddr_guard != MAP_FAILED) {
|
||||
munmap(vaddr_guard, size);
|
||||
@@ -137,9 +142,9 @@ void CircularBuffer::dealloc() noexcept {
|
||||
if (mData) {
|
||||
size_t const BLOCK_SIZE = getBlockSize();
|
||||
munmap(mData, mSize * 2 + BLOCK_SIZE);
|
||||
if (mUsesAshmem >= 0) {
|
||||
close(mUsesAshmem);
|
||||
mUsesAshmem = -1;
|
||||
if (mAshmemFd >= 0) {
|
||||
close(mAshmemFd);
|
||||
mAshmemFd = -1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -149,23 +154,37 @@ void CircularBuffer::dealloc() noexcept {
|
||||
}
|
||||
|
||||
|
||||
void CircularBuffer::circularize() noexcept {
|
||||
if (mUsesAshmem > 0) {
|
||||
intptr_t const overflow = intptr_t(mHead) - (intptr_t(mData) + ssize_t(mSize));
|
||||
if (overflow >= 0) {
|
||||
assert_invariant(size_t(overflow) <= mSize);
|
||||
mHead = (void *) (intptr_t(mData) + overflow);
|
||||
#ifndef NDEBUG
|
||||
memset(mData, 0xA5, size_t(overflow));
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// Only circularize if mHead if in the second buffer.
|
||||
if (intptr_t(mHead) - intptr_t(mData) > ssize_t(mSize)) {
|
||||
CircularBuffer::Range CircularBuffer::getBuffer() noexcept {
|
||||
Range const range{ .tail = mTail, .head = mHead };
|
||||
|
||||
char* const pData = static_cast<char*>(mData);
|
||||
char const* const pEnd = pData + mSize;
|
||||
char const* const pHead = static_cast<char const*>(mHead);
|
||||
if (UTILS_UNLIKELY(pHead >= pEnd)) {
|
||||
size_t const overflow = pHead - pEnd;
|
||||
if (UTILS_LIKELY(mAshmemFd > 0)) {
|
||||
assert_invariant(overflow <= mSize);
|
||||
mHead = static_cast<void*>(pData + overflow);
|
||||
// Data Tail End Head [virtual]
|
||||
// v v v v
|
||||
// +-------------:----+-----:--------------+
|
||||
// | : | : |
|
||||
// +-----:------------+--------------------+
|
||||
// Head |<------ copy ------>| [physical]
|
||||
} else {
|
||||
// Data Tail End Head
|
||||
// v v v v
|
||||
// +-------------:----+-----:--------------+
|
||||
// | : | : |
|
||||
// +-----|------------+-----|--------------+
|
||||
// |<---------------->|
|
||||
// sliding window
|
||||
mHead = mData;
|
||||
}
|
||||
}
|
||||
mTail = mHead;
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -15,14 +15,25 @@
|
||||
*/
|
||||
|
||||
#include "private/backend/CommandBufferQueue.h"
|
||||
#include "private/backend/CircularBuffer.h"
|
||||
#include "private/backend/CommandStream.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include "private/backend/BackendUtils.h"
|
||||
#include "private/backend/CommandStream.h"
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
@@ -65,50 +76,53 @@ void CommandBufferQueue::flush() noexcept {
|
||||
// always guaranteed to have enough space for the NoopCommand
|
||||
new(circularBuffer.allocate(sizeof(NoopCommand))) NoopCommand(nullptr);
|
||||
|
||||
// end of this slice
|
||||
void* const head = circularBuffer.getHead();
|
||||
const size_t requiredSize = mRequiredSize;
|
||||
|
||||
// beginning of this slice
|
||||
void* const tail = circularBuffer.getTail();
|
||||
// get the current buffer
|
||||
auto const [begin, end] = circularBuffer.getBuffer();
|
||||
|
||||
// size of this slice
|
||||
uint32_t const used = uint32_t(intptr_t(head) - intptr_t(tail));
|
||||
assert_invariant(circularBuffer.empty());
|
||||
|
||||
circularBuffer.circularize();
|
||||
// size of the current buffer
|
||||
size_t const used = std::distance(
|
||||
static_cast<char const*>(begin), static_cast<char const*>(end));
|
||||
|
||||
std::unique_lock<utils::Mutex> lock(mLock);
|
||||
mCommandBuffersToExecute.push_back({ tail, head });
|
||||
mCommandBuffersToExecute.push_back({ begin, end });
|
||||
mCondition.notify_one();
|
||||
|
||||
// circular buffer is too small, we corrupted the stream
|
||||
ASSERT_POSTCONDITION(used <= mFreeSpace,
|
||||
"Backend CommandStream overflow. Commands are corrupted and unrecoverable.\n"
|
||||
"Please increase minCommandBufferSizeMB inside the Config passed to Engine::create.\n"
|
||||
"Space used at this time: %u bytes",
|
||||
(unsigned)used);
|
||||
"Space used at this time: %u bytes, overflow: %u bytes",
|
||||
(unsigned)used, unsigned(used - mFreeSpace));
|
||||
|
||||
// wait until there is enough space in the buffer
|
||||
mFreeSpace -= used;
|
||||
const size_t requiredSize = mRequiredSize;
|
||||
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t totalUsed = circularBuffer.size() - mFreeSpace;
|
||||
mHighWatermark = std::max(mHighWatermark, totalUsed);
|
||||
if (UTILS_UNLIKELY(totalUsed > requiredSize)) {
|
||||
slog.d << "CommandStream used too much space: " << totalUsed
|
||||
<< ", out of " << requiredSize << " (will block)" << io::endl;
|
||||
}
|
||||
size_t const totalUsed = circularBuffer.size() - mFreeSpace;
|
||||
slog.d << "CommandStream used too much space (will block): "
|
||||
<< "needed space " << requiredSize << " out of " << mFreeSpace
|
||||
<< ", totalUsed=" << totalUsed << ", current=" << used
|
||||
<< ", queue size=" << mCommandBuffersToExecute.size() << " buffers"
|
||||
<< io::endl;
|
||||
|
||||
mHighWatermark = std::max(mHighWatermark, totalUsed);
|
||||
#endif
|
||||
|
||||
mCondition.notify_one();
|
||||
if (UTILS_LIKELY(mFreeSpace < requiredSize)) {
|
||||
SYSTRACE_NAME("waiting: CircularBuffer::flush()");
|
||||
mCondition.wait(lock, [this, requiredSize]() -> bool {
|
||||
// TODO: on macOS, we need to call pumpEvents from time to time
|
||||
return mFreeSpace >= requiredSize;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CommandBufferQueue::Slice> CommandBufferQueue::waitForCommands() const {
|
||||
std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() const {
|
||||
if (!UTILS_HAS_THREADING) {
|
||||
return std::move(mCommandBuffersToExecute);
|
||||
}
|
||||
@@ -123,7 +137,7 @@ std::vector<CommandBufferQueue::Slice> CommandBufferQueue::waitForCommands() con
|
||||
return std::move(mCommandBuffersToExecute);
|
||||
}
|
||||
|
||||
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Slice const& buffer) {
|
||||
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) {
|
||||
std::lock_guard<utils::Mutex> const lock(mLock);
|
||||
mFreeSpace += uintptr_t(buffer.end) - uintptr_t(buffer.begin);
|
||||
mCondition.notify_one();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
|
||||
#include <backend/AcquiredImage.h>
|
||||
#include <backend/BufferDescriptor.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <math/half.h>
|
||||
@@ -29,6 +34,13 @@
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
using namespace utils;
|
||||
using namespace filament::math;
|
||||
|
||||
@@ -119,7 +131,8 @@ void DriverBase::purge() noexcept {
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void DriverBase::scheduleDestroySlow(BufferDescriptor&& buffer) noexcept {
|
||||
scheduleCallback(buffer.getHandler(), [buffer = std::move(buffer)]() {
|
||||
auto const handler = buffer.getHandler();
|
||||
scheduleCallback(handler, [buffer = std::move(buffer)]() {
|
||||
// user callback is called when BufferDescriptor gets destroyed
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
#include <tsl/robin_map.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
namespace filament::backend {
|
||||
|
||||
struct MetalContext;
|
||||
|
||||
@@ -37,8 +36,7 @@ public:
|
||||
|
||||
struct BlitArgs {
|
||||
struct Attachment {
|
||||
id<MTLTexture> color = nil;
|
||||
id<MTLTexture> depth = nil;
|
||||
id<MTLTexture> texture = nil;
|
||||
MTLRegion region = {};
|
||||
uint8_t level = 0;
|
||||
uint32_t slice = 0; // must be 0 on source attachment
|
||||
@@ -46,25 +44,13 @@ public:
|
||||
|
||||
// Valid source formats: 2D, 2DArray, 2DMultisample, 3D
|
||||
// Valid destination formats: 2D, 2DArray, 3D, Cube
|
||||
Attachment source, destination;
|
||||
Attachment source;
|
||||
Attachment destination;
|
||||
SamplerMagFilter filter;
|
||||
|
||||
bool blitColor() const {
|
||||
return source.color != nil && destination.color != nil;
|
||||
}
|
||||
|
||||
bool blitDepth() const {
|
||||
return source.depth != nil && destination.depth != nil;
|
||||
}
|
||||
|
||||
bool colorDestinationIsFullAttachment() const {
|
||||
return destination.color.width == destination.region.size.width &&
|
||||
destination.color.height == destination.region.size.height;
|
||||
}
|
||||
|
||||
bool depthDestinationIsFullAttachment() const {
|
||||
return destination.depth.width == destination.region.size.width &&
|
||||
destination.depth.height == destination.region.size.height;
|
||||
bool destinationIsFullAttachment() const {
|
||||
return destination.texture.width == destination.region.size.width &&
|
||||
destination.texture.height == destination.region.size.height;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,48 +64,36 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
static void setupColorAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane);
|
||||
static void setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane);
|
||||
static void setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor,
|
||||
const BlitArgs& args, uint32_t depthPlane);
|
||||
|
||||
struct BlitFunctionKey {
|
||||
bool blitColor;
|
||||
bool blitDepth;
|
||||
bool msaaColorSource;
|
||||
bool msaaDepthSource;
|
||||
bool sources3D;
|
||||
|
||||
char padding[3];
|
||||
bool msaaColorSource{};
|
||||
bool sources3D{};
|
||||
char padding[2]{};
|
||||
|
||||
bool isValid() const noexcept {
|
||||
// MSAA 3D textures do not exist.
|
||||
bool hasMsaa = msaaColorSource || msaaDepthSource;
|
||||
bool const hasMsaa = msaaColorSource;
|
||||
return !(hasMsaa && sources3D);
|
||||
}
|
||||
|
||||
bool operator==(const BlitFunctionKey& rhs) const noexcept {
|
||||
return blitColor == rhs.blitColor &&
|
||||
blitDepth == rhs.blitDepth &&
|
||||
msaaColorSource == rhs.msaaColorSource &&
|
||||
msaaDepthSource == rhs.msaaDepthSource &&
|
||||
return msaaColorSource == rhs.msaaColorSource &&
|
||||
sources3D == rhs.sources3D;
|
||||
}
|
||||
|
||||
BlitFunctionKey() {
|
||||
std::memset(this, 0, sizeof(BlitFunctionKey));
|
||||
}
|
||||
};
|
||||
|
||||
void blitFastPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
static bool blitFastPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label);
|
||||
void blitSlowPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
|
||||
void blitSlowPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label);
|
||||
void blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor, bool blitDepth,
|
||||
const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest,
|
||||
const char* label);
|
||||
id<MTLTexture> createIntermediateTexture(id<MTLTexture> t, MTLSize size);
|
||||
id<MTLFunction> compileFragmentFunction(BlitFunctionKey key);
|
||||
|
||||
void blitDepthPlane(id <MTLCommandBuffer> cmdBuffer, const BlitArgs& args,
|
||||
uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label);
|
||||
|
||||
id<MTLFunction> compileFragmentFunction(BlitFunctionKey key) const;
|
||||
id<MTLFunction> getBlitVertexFunction();
|
||||
id<MTLFunction> getBlitFragmentFunction(BlitFunctionKey key);
|
||||
|
||||
@@ -130,10 +104,9 @@ private:
|
||||
tsl::robin_map<BlitFunctionKey, Function, HashFn> mBlitFunctions;
|
||||
|
||||
id<MTLFunction> mVertexFunction = nil;
|
||||
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
#endif //TNT_METALBLITTER_H
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
#include "MetalUtils.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
namespace filament::backend {
|
||||
|
||||
static const char* functionLibrary = R"(
|
||||
#include <metal_stdlib>
|
||||
@@ -37,13 +37,7 @@ struct VertexOut
|
||||
|
||||
struct FragmentOut
|
||||
{
|
||||
#ifdef BLIT_COLOR
|
||||
float4 color [[color(0)]];
|
||||
#endif
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
float depth [[depth(any)]];
|
||||
#endif
|
||||
};
|
||||
|
||||
vertex VertexOut
|
||||
@@ -67,7 +61,6 @@ fragment FragmentOut
|
||||
blitterFrag(VertexOut in [[stage_in]],
|
||||
sampler sourceSampler [[sampler(0)]],
|
||||
|
||||
#ifdef BLIT_COLOR
|
||||
#ifdef MSAA_COLOR_SOURCE
|
||||
texture2d_ms<float, access::read> sourceColor [[texture(0)]],
|
||||
#elif SOURCES_3D
|
||||
@@ -75,32 +68,18 @@ blitterFrag(VertexOut in [[stage_in]],
|
||||
#else
|
||||
texture2d<float, access::sample> sourceColor [[texture(0)]],
|
||||
#endif // MSAA_COLOR_SOURCE
|
||||
#endif // BLIT_COLOR
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
#ifdef MSAA_DEPTH_SOURCE
|
||||
texture2d_ms<float, access::read> sourceDepth [[texture(1)]],
|
||||
#elif SOURCES_3D
|
||||
texture3d<float, access::sample> sourceDepth [[texture(1)]],
|
||||
#else
|
||||
texture2d<float, access::sample> sourceDepth [[texture(1)]],
|
||||
#endif // MSAA_DEPTH_SOURCE
|
||||
#endif // BLIT_DEPTH
|
||||
|
||||
constant FragmentArgs* args [[buffer(0)]])
|
||||
{
|
||||
FragmentOut out = {};
|
||||
|
||||
#if defined(BLIT_COLOR) || defined(BLIT_DEPTH)
|
||||
// These coordinates match the Vulkan vkCmdBlitImage spec:
|
||||
// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdBlitImage.html
|
||||
float2 uvbase = in.position.xy; // unnormalized coordinates at center of texel: (1.5, 2.5, etc)
|
||||
float2 uvoffset = uvbase - args->dstOffset;
|
||||
float2 uvscaled = uvoffset * args->scale;
|
||||
float2 uv = uvscaled + args->srcOffset;
|
||||
#endif
|
||||
|
||||
#ifdef BLIT_COLOR
|
||||
#ifdef MSAA_COLOR_SOURCE
|
||||
out.color = float4(0.0);
|
||||
for (uint s = 0; s < sourceColor.get_num_samples(); s++) {
|
||||
@@ -116,201 +95,106 @@ blitterFrag(VertexOut in [[stage_in]],
|
||||
float2 uvnorm = uv / float2(sourceColor.get_width(args->lod), sourceColor.get_height(args->lod));
|
||||
out.color += sourceColor.sample(sourceSampler, uvnorm, level(args->lod));
|
||||
#endif // MSAA_COLOR_SOURCE
|
||||
#endif // BLIT_COLOR
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
#ifdef MSAA_DEPTH_SOURCE
|
||||
out.depth = 0.0;
|
||||
for (uint s = 0; s < sourceDepth.get_num_samples(); s++) {
|
||||
out.depth += sourceDepth.read(static_cast<uint2>(uv), s).r;
|
||||
}
|
||||
out.depth /= sourceDepth.get_num_samples();
|
||||
#elif SOURCES_3D
|
||||
float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod));
|
||||
float3 coords = float3(uvnormd, (static_cast<float>(args->depthPlane) + 0.5) /
|
||||
sourceDepth.get_depth(args->lod));
|
||||
out.depth = sourceDepth.sample(sourceSampler, coords, level(args->lod)).r;
|
||||
#else
|
||||
float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod));
|
||||
out.depth = sourceDepth.sample(sourceSampler, uvnormd, level(args->lod)).r;
|
||||
#endif // MSAA_DEPTH_SOURCE
|
||||
#endif // BLIT_DEPTH
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline bool MTLSizeEqual(T a, T b) noexcept {
|
||||
return (a.width == b.width && a.height == b.height && a.depth == b.depth);
|
||||
}
|
||||
|
||||
MetalBlitter::MetalBlitter(MetalContext& context) noexcept : mContext(context) { }
|
||||
|
||||
#define MTLSizeEqual(a, b) (a.width == b.width && a.height == b.height && a.depth == b.depth)
|
||||
|
||||
void MetalBlitter::blit(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args, const char* label) {
|
||||
bool blitColor = args.blitColor();
|
||||
bool blitDepth = args.blitDepth();
|
||||
|
||||
ASSERT_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth,
|
||||
"Blitting requires the source and destination regions to have the same depth.");
|
||||
|
||||
if (args.source.color && args.source.depth) {
|
||||
MTLTextureType colorType = args.source.color.textureType;
|
||||
MTLTextureType depthType = args.source.depth.textureType;
|
||||
|
||||
if (colorType == MTLTextureType2DMultisample) colorType = MTLTextureType2D;
|
||||
if (depthType == MTLTextureType2DMultisample) depthType = MTLTextureType2D;
|
||||
|
||||
ASSERT_PRECONDITION(colorType == depthType,
|
||||
"Blitting requires color and depth sources to be the same texture type.");
|
||||
}
|
||||
|
||||
// Determine if the blit for color or depth are eligible to use a MTLBlitCommandEncoder.
|
||||
// blitColor and / or blitDepth are set to false upon success, to indicate that no more work is
|
||||
// necessary for that attachment.
|
||||
blitFastPath(cmdBuffer, blitColor, blitDepth, args, label);
|
||||
|
||||
if (!blitColor && !blitDepth) {
|
||||
// blitFastPath returns true upon success.
|
||||
// blitFastPath fails if either the format or sampleCount don't match (i.e. resolve)
|
||||
if (blitFastPath(cmdBuffer, args, label)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the destination is MSAA and we weren't able to use the fast path, report an error, as
|
||||
// blitting to a MSAA texture isn't supported through the "slow path" yet.
|
||||
const bool colorDestinationIsMultisample =
|
||||
blitColor && args.destination.color.textureType == MTLTextureType2DMultisample;
|
||||
const bool depthDestinationIsMultisample =
|
||||
blitDepth && args.destination.depth.textureType == MTLTextureType2DMultisample;
|
||||
ASSERT_PRECONDITION(!colorDestinationIsMultisample && !depthDestinationIsMultisample,
|
||||
"Blitting between MSAA render targets with differing pixel formats and/or regions is not supported.");
|
||||
// If we end-up here, it means that either:
|
||||
// - we're resolving a color texture
|
||||
// - src/dest formats didn't match, or we're scaling -- this can only happen with the legacy
|
||||
// blit() path and implies that the format is not depth.
|
||||
// note: in the future we will support a fast-path resolve
|
||||
|
||||
// If the destination texture doesn't have the MTLTextureUsageRenderTarget flag, we have to blit
|
||||
// to an intermediate texture first to perform the format conversion. Then, we can perform a
|
||||
// "fast blit" to the final destination texture.
|
||||
UTILS_UNUSED_IN_RELEASE
|
||||
const bool destinationIsMultisample =
|
||||
args.destination.texture.textureType == MTLTextureType2DMultisample;
|
||||
|
||||
id<MTLTexture> intermediateColor = nil;
|
||||
id<MTLTexture> intermediateDepth = nil;
|
||||
BlitArgs slowBlit = args;
|
||||
BlitArgs finalBlit = args;
|
||||
|
||||
MTLRegion sourceRegionNoOffset = MTLRegionMake3D(0, 0, 0,
|
||||
args.source.region.size.width, args.source.region.size.height,
|
||||
args.source.region.size.depth);
|
||||
|
||||
if (blitColor && !(args.destination.color.usage & MTLTextureUsageRenderTarget)) {
|
||||
intermediateColor = createIntermediateTexture(args.destination.color, args.source.region.size);
|
||||
slowBlit.destination.color = finalBlit.source.color = intermediateColor;
|
||||
slowBlit.destination.level = finalBlit.source.level = 0;
|
||||
slowBlit.destination.slice = finalBlit.source.slice = 0;
|
||||
slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset;
|
||||
}
|
||||
if (blitDepth && !(args.destination.depth.usage & MTLTextureUsageRenderTarget)) {
|
||||
intermediateDepth = createIntermediateTexture(args.destination.depth, args.source.region.size);
|
||||
slowBlit.destination.depth = finalBlit.source.depth = intermediateDepth;
|
||||
slowBlit.destination.level = finalBlit.source.level = 0;
|
||||
slowBlit.destination.slice = finalBlit.source.slice = 0;
|
||||
slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset;
|
||||
}
|
||||
|
||||
blitSlowPath(cmdBuffer, blitColor, blitDepth, slowBlit, label);
|
||||
|
||||
bool finalBlitColor = intermediateColor != nil;
|
||||
bool finalBlitDepth = intermediateDepth != nil;
|
||||
blitFastPath(cmdBuffer, finalBlitColor, finalBlitDepth, finalBlit, label);
|
||||
assert_invariant(!destinationIsMultisample);
|
||||
assert_invariant((args.destination.texture.usage & MTLTextureUsageRenderTarget));
|
||||
blitSlowPath(cmdBuffer, args, label);
|
||||
}
|
||||
|
||||
void MetalBlitter::blitFastPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
bool MetalBlitter::blitFastPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label) {
|
||||
if (blitColor) {
|
||||
if (args.source.color.sampleCount == args.destination.color.sampleCount &&
|
||||
args.source.color.pixelFormat == args.destination.color.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.color
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.color
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
if (args.source.texture.sampleCount == args.destination.texture.sampleCount &&
|
||||
args.source.texture.pixelFormat == args.destination.texture.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
blitColor = false;
|
||||
}
|
||||
}
|
||||
id<MTLBlitCommandEncoder> const blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.texture
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.texture
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
if (blitDepth) {
|
||||
if (args.source.depth.sampleCount == args.destination.depth.sampleCount &&
|
||||
args.source.depth.pixelFormat == args.destination.depth.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.depth
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.depth
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
blitDepth = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetalBlitter::blitSlowPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
void MetalBlitter::blitSlowPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label) {
|
||||
|
||||
// scaling in any dimension is not allowed
|
||||
assert_invariant(args.source.region.size.depth == args.destination.region.size.depth);
|
||||
// we're always blitting a single plane
|
||||
uint32_t depthPlaneSource = args.source.region.origin.z;
|
||||
uint32_t depthPlaneDest = args.destination.region.origin.z;
|
||||
|
||||
assert_invariant(args.source.region.size.depth == args.destination.region.size.depth);
|
||||
uint32_t depthPlaneCount = args.source.region.size.depth;
|
||||
for (NSUInteger d = 0; d < depthPlaneCount; d++) {
|
||||
blitDepthPlane(cmdBuffer, blitColor, blitDepth, args, depthPlaneSource++, depthPlaneDest++,
|
||||
label);
|
||||
for (NSUInteger d = 0; d < args.source.region.size.depth; d++) {
|
||||
blitDepthPlane(cmdBuffer, args,
|
||||
depthPlaneSource++, depthPlaneDest++, label);
|
||||
}
|
||||
|
||||
blitColor = false;
|
||||
blitDepth = false;
|
||||
}
|
||||
|
||||
void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor, bool blitDepth,
|
||||
const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest,
|
||||
const char* label) {
|
||||
MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args,
|
||||
uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label) {
|
||||
|
||||
if (blitColor) {
|
||||
setupColorAttachment(args, descriptor, depthPlaneDest);
|
||||
}
|
||||
MTLRenderPassDescriptor* const descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
|
||||
if (blitDepth) {
|
||||
setupDepthAttachment(args, descriptor, depthPlaneDest);
|
||||
}
|
||||
setupAttachment(descriptor.colorAttachments[0], args, depthPlaneDest);
|
||||
|
||||
id<MTLRenderCommandEncoder> encoder = [cmdBuffer renderCommandEncoderWithDescriptor:descriptor];
|
||||
id<MTLRenderCommandEncoder> const encoder =
|
||||
[cmdBuffer renderCommandEncoderWithDescriptor:descriptor];
|
||||
encoder.label = @(label);
|
||||
|
||||
BlitFunctionKey key;
|
||||
key.blitColor = blitColor;
|
||||
key.blitDepth = blitDepth;
|
||||
key.msaaColorSource = blitColor && args.source.color.textureType == MTLTextureType2DMultisample;
|
||||
key.msaaDepthSource = blitDepth && args.source.depth.textureType == MTLTextureType2DMultisample;
|
||||
key.sources3D = blitColor && args.source.color.textureType == MTLTextureType3D;
|
||||
if (key.sources3D && blitDepth) {
|
||||
assert_invariant(args.source.depth.textureType == MTLTextureType3D);
|
||||
}
|
||||
id<MTLFunction> fragmentFunction = getBlitFragmentFunction(key);
|
||||
key.msaaColorSource = args.source.texture.textureType == MTLTextureType2DMultisample;
|
||||
key.sources3D = args.source.texture.textureType == MTLTextureType3D;
|
||||
id<MTLFunction> const fragmentFunction = getBlitFragmentFunction(key);
|
||||
|
||||
MetalPipelineState pipelineState {
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = getBlitVertexFunction(),
|
||||
.fragmentFunction = fragmentFunction,
|
||||
.vertexDescription = {},
|
||||
.colorAttachmentPixelFormat = {
|
||||
blitColor ? args.destination.color.pixelFormat : MTLPixelFormatInvalid,
|
||||
args.destination.texture.pixelFormat,
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid,
|
||||
@@ -319,31 +203,21 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid
|
||||
},
|
||||
.depthAttachmentPixelFormat =
|
||||
blitDepth ? args.destination.depth.pixelFormat : MTLPixelFormatInvalid,
|
||||
.depthAttachmentPixelFormat = MTLPixelFormatInvalid,
|
||||
.sampleCount = 1,
|
||||
.blendState = {}
|
||||
};
|
||||
id<MTLRenderPipelineState> pipeline = mContext.pipelineStateCache.getOrCreateState(pipelineState);
|
||||
id<MTLRenderPipelineState> const pipeline =
|
||||
mContext.pipelineStateCache.getOrCreateState(pipelineState);
|
||||
[encoder setRenderPipelineState:pipeline];
|
||||
|
||||
// For texture arrays, create a view of the texture at the given slice (layer).
|
||||
id<MTLTexture> srcTextureColor = args.source.color;
|
||||
|
||||
id<MTLTexture> srcTextureColor = args.source.texture;
|
||||
if (srcTextureColor && srcTextureColor.textureType == MTLTextureType2DArray) {
|
||||
srcTextureColor = createTextureViewWithSingleSlice(srcTextureColor, args.source.slice);
|
||||
}
|
||||
id<MTLTexture> srcTextureDepth = args.source.depth;
|
||||
if (srcTextureDepth && srcTextureDepth.textureType == MTLTextureType2DArray) {
|
||||
srcTextureDepth = createTextureViewWithSingleSlice(srcTextureDepth, args.source.slice);
|
||||
}
|
||||
|
||||
if (blitColor) {
|
||||
[encoder setFragmentTexture:srcTextureColor atIndex:0];
|
||||
}
|
||||
|
||||
if (blitDepth) {
|
||||
[encoder setFragmentTexture:srcTextureDepth atIndex:1];
|
||||
}
|
||||
[encoder setFragmentTexture:srcTextureColor atIndex:0];
|
||||
|
||||
SamplerMinFilter filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
|
||||
if (args.filter == SamplerMagFilter::NEAREST) {
|
||||
@@ -352,13 +226,13 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
|
||||
}
|
||||
|
||||
SamplerState s {
|
||||
SamplerState const s {
|
||||
.samplerParams = {
|
||||
.filterMag = args.filter,
|
||||
.filterMin = filterMin
|
||||
}
|
||||
};
|
||||
id<MTLSamplerState> sampler = mContext.samplerStateCache.getOrCreateState(s);
|
||||
id<MTLSamplerState> const sampler = mContext.samplerStateCache.getOrCreateState(s);
|
||||
[encoder setFragmentSamplerState:sampler atIndex:0];
|
||||
|
||||
MTLViewport viewport;
|
||||
@@ -370,11 +244,11 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
viewport.zfar = 1.0;
|
||||
[encoder setViewport:viewport];
|
||||
|
||||
DepthStencilState depthStencilState {
|
||||
DepthStencilState const depthStencilState {
|
||||
.depthCompare = MTLCompareFunctionAlways,
|
||||
.depthWriteEnabled = blitDepth
|
||||
.depthWriteEnabled = false
|
||||
};
|
||||
id<MTLDepthStencilState> depthStencil =
|
||||
id<MTLDepthStencilState> const depthStencil =
|
||||
mContext.depthStencilStateCache.getOrCreateState(depthStencilState);
|
||||
[encoder setDepthStencilState:depthStencil];
|
||||
|
||||
@@ -410,80 +284,42 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
[encoder endEncoding];
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalBlitter::createIntermediateTexture(id<MTLTexture> t, MTLSize size) {
|
||||
MTLTextureDescriptor* descriptor = [MTLTextureDescriptor new];
|
||||
descriptor.textureType = size.depth == 1 ? MTLTextureType2D : MTLTextureType3D;
|
||||
descriptor.pixelFormat = t.pixelFormat;
|
||||
descriptor.width = size.width;
|
||||
descriptor.height = size.height;
|
||||
descriptor.depth = size.depth;
|
||||
descriptor.usage = t.usage & MTLTextureUsageRenderTarget;
|
||||
return [mContext.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
void MetalBlitter::shutdown() noexcept {
|
||||
mBlitFunctions.clear();
|
||||
mVertexFunction = nil;
|
||||
}
|
||||
|
||||
void MetalBlitter::setupColorAttachment(const BlitArgs& args,
|
||||
MTLRenderPassDescriptor* descriptor, uint32_t depthPlane) {
|
||||
descriptor.colorAttachments[0].texture = args.destination.color;
|
||||
descriptor.colorAttachments[0].level = args.destination.level;
|
||||
descriptor.colorAttachments[0].slice = args.destination.slice;
|
||||
descriptor.colorAttachments[0].depthPlane = depthPlane;
|
||||
|
||||
descriptor.colorAttachments[0].loadAction = MTLLoadActionLoad;
|
||||
void MetalBlitter::setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor,
|
||||
const BlitArgs& args, uint32_t depthPlane) {
|
||||
descriptor.texture = args.destination.texture;
|
||||
descriptor.level = args.destination.level;
|
||||
descriptor.slice = args.destination.slice;
|
||||
descriptor.depthPlane = depthPlane;
|
||||
descriptor.loadAction = MTLLoadActionLoad;
|
||||
// We don't need to load the contents of the attachment if we're blitting over all of it.
|
||||
if (args.colorDestinationIsFullAttachment()) {
|
||||
descriptor.colorAttachments[0].loadAction = MTLLoadActionDontCare;
|
||||
if (args.destinationIsFullAttachment()) {
|
||||
descriptor.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
descriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
descriptor.storeAction = MTLStoreActionStore;
|
||||
}
|
||||
|
||||
void MetalBlitter::setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane) {
|
||||
descriptor.depthAttachment.texture = args.destination.depth;
|
||||
descriptor.depthAttachment.level = args.destination.level;
|
||||
descriptor.depthAttachment.slice = args.destination.slice;
|
||||
descriptor.depthAttachment.depthPlane = depthPlane;
|
||||
|
||||
descriptor.depthAttachment.loadAction = MTLLoadActionLoad;
|
||||
// We don't need to load the contents of the attachment if we're blitting over all of it.
|
||||
if (args.depthDestinationIsFullAttachment()) {
|
||||
descriptor.depthAttachment.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
descriptor.depthAttachment.storeAction = MTLStoreActionStore;
|
||||
}
|
||||
|
||||
id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) {
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
NSMutableDictionary* macros = [NSMutableDictionary dictionary];
|
||||
if (key.blitColor) {
|
||||
macros[@"BLIT_COLOR"] = @"1";
|
||||
}
|
||||
if (key.blitDepth) {
|
||||
macros[@"BLIT_DEPTH"] = @"1";
|
||||
}
|
||||
id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) const {
|
||||
MTLCompileOptions* const options = [MTLCompileOptions new];
|
||||
NSMutableDictionary* const macros = [NSMutableDictionary dictionary];
|
||||
if (key.msaaColorSource) {
|
||||
macros[@"MSAA_COLOR_SOURCE"] = @"1";
|
||||
}
|
||||
if (key.msaaDepthSource) {
|
||||
macros[@"MSAA_DEPTH_SOURCE"] = @"1";
|
||||
}
|
||||
if (key.sources3D) {
|
||||
macros[@"SOURCES_3D"] = @"1";
|
||||
}
|
||||
options.preprocessorMacros = macros;
|
||||
NSString* objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSString* const objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"blitterFrag"];
|
||||
id <MTLLibrary> const library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
id <MTLFunction> const function = [library newFunctionWithName:@"blitterFrag"];
|
||||
|
||||
if (!library || !function) {
|
||||
if (error) {
|
||||
@@ -501,13 +337,14 @@ id<MTLFunction> MetalBlitter::getBlitVertexFunction() {
|
||||
return mVertexFunction;
|
||||
}
|
||||
|
||||
NSString* objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSString* const objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"blitterVertex"];
|
||||
id <MTLLibrary> const library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
|
||||
id<MTLFunction> const function = [library newFunctionWithName:@"blitterVertex"];
|
||||
|
||||
if (!library || !function) {
|
||||
if (error) {
|
||||
@@ -535,5 +372,5 @@ id<MTLFunction> MetalBlitter::getBlitFragmentFunction(BlitFunctionKey key) {
|
||||
return function;
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -73,7 +73,10 @@ private:
|
||||
std::unordered_set<MetalBufferPoolEntry const*> mUsedStages;
|
||||
|
||||
// Store the current "time" (really just a frame count) and LRU eviction parameters.
|
||||
uint64_t mCurrentFrame = 0;
|
||||
// An atomic is necessary as mCurrentFrame is incremented in gc() (called on
|
||||
// the driver thread) and read from acquireBuffer() and releaseBuffer(),
|
||||
// which may be called on non-driver threads.
|
||||
std::atomic<uint64_t> mCurrentFrame = 0;
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = 10;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "MetalContext.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/trap.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
@@ -142,7 +142,10 @@ struct MetalContext {
|
||||
// Fences, only supported on macOS 10.14 and iOS 12 and above.
|
||||
API_AVAILABLE(macos(10.14), ios(12.0))
|
||||
MTLSharedEventListener* eventListener = nil;
|
||||
uint64_t signalId = 1;
|
||||
// signalId is incremented in the MetalFence constructor, which is called on
|
||||
// both the driver (MetalTimerQueryFence::beginTimeElapsedQuery) and main
|
||||
// threads (in createFenceS), so an atomic is necessary.
|
||||
std::atomic<uint64_t> signalId = 1;
|
||||
|
||||
MetalTimerQueryInterface* timerQueryImpl;
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
@@ -52,13 +56,13 @@ class MetalDriver final : public DriverBase {
|
||||
|
||||
public:
|
||||
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
|
||||
void runAtNextTick(const std::function<void()>& fn) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
friend class MetalSwapChain;
|
||||
|
||||
MetalPlatform& mPlatform;
|
||||
|
||||
MetalContext* mContext;
|
||||
|
||||
ShaderModel getShaderModel() const noexcept final;
|
||||
@@ -66,6 +70,13 @@ private:
|
||||
// Overrides the default implementation by wrapping the call to fn in an @autoreleasepool block.
|
||||
void execute(std::function<void(void)> const& fn) noexcept final;
|
||||
|
||||
/*
|
||||
* Tasks run regularly on the driver thread.
|
||||
*/
|
||||
void executeTickOps() noexcept;
|
||||
std::vector<std::function<void()>> mTickOps;
|
||||
std::mutex mTickOpsLock;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
*/
|
||||
|
||||
@@ -166,6 +166,7 @@ MetalDriver::~MetalDriver() noexcept {
|
||||
}
|
||||
|
||||
void MetalDriver::tick(int) {
|
||||
executeTickOps();
|
||||
}
|
||||
|
||||
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
@@ -583,6 +584,8 @@ void MetalDriver::terminate() {
|
||||
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
|
||||
finish();
|
||||
|
||||
executeTickOps();
|
||||
|
||||
mContext->bufferPool->reset();
|
||||
mContext->commandQueue = nil;
|
||||
|
||||
@@ -727,6 +730,10 @@ bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -865,10 +872,6 @@ void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
tex->generateMipmaps();
|
||||
}
|
||||
|
||||
bool MetalDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescriptor&& data) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"updateSamplerGroup must be called outside of a render pass.");
|
||||
@@ -1256,14 +1259,14 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
||||
textureDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget;
|
||||
id<MTLTexture> readPixelsTexture = [mContext->device newTextureWithDescriptor:textureDescriptor];
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.level = miplevel;
|
||||
args.source.region = MTLRegionMake2D(0, 0, srcTexture.width >> miplevel, srcTexture.height >> miplevel);
|
||||
args.source.texture = srcTexture;
|
||||
args.destination.level = 0;
|
||||
args.destination.region = MTLRegionMake2D(0, 0, readPixelsTexture.width, readPixelsTexture.height);
|
||||
args.source.color = srcTexture;
|
||||
args.destination.color = readPixelsTexture;
|
||||
args.destination.texture = readPixelsTexture;
|
||||
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "readPixels blit");
|
||||
|
||||
@@ -1299,24 +1302,123 @@ void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
void MetalDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
auto* const srcTexture = handle_cast<MetalTexture>(src);
|
||||
auto* const dstTexture = handle_cast<MetalTexture>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"resolve() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+srcTexture->samples, +dstTexture->samples);
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format),
|
||||
"can't resolve depth formats");
|
||||
|
||||
ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format),
|
||||
"can't resolve stencil formats");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
// FIXME: on metal the blit() call below always take the slow path (using a shader)
|
||||
|
||||
blit( dst, dstLevel, dstLayer, {},
|
||||
src, srcLevel, srcLayer, {},
|
||||
{ dstTexture->width, dstTexture->height });
|
||||
}
|
||||
|
||||
void MetalDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
|
||||
|
||||
auto isBlitableTextureType = [](MTLTextureType t) -> bool {
|
||||
return t == MTLTextureType2D || t == MTLTextureType2DMultisample ||
|
||||
t == MTLTextureType2DArray;
|
||||
};
|
||||
|
||||
auto* const srcTexture = handle_cast<MetalTexture>(src);
|
||||
auto* const dstTexture = handle_cast<MetalTexture>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blit() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcTexture->getMtlTextureForRead().textureType) &&
|
||||
isBlitableTextureType(dstTexture->getMtlTextureForWrite().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.region = MTLRegionMake2D(
|
||||
(NSUInteger)srcOrigin.x,
|
||||
std::max(srcTexture->height - (int64_t)srcOrigin.y - size.y, (int64_t)0),
|
||||
size.x, size.y);
|
||||
args.destination.region = MTLRegionMake2D(
|
||||
(NSUInteger)dstOrigin.x,
|
||||
std::max(dstTexture->height - (int64_t)dstOrigin.y - size.y, (int64_t)0),
|
||||
size.x, size.y);
|
||||
|
||||
// FIXME: we shouldn't need to know the type here. This is an artifact of using the old API.
|
||||
|
||||
args.source.texture = srcTexture->getMtlTextureForRead();
|
||||
args.destination.texture = dstTexture->getMtlTextureForWrite();
|
||||
args.source.level = srcLevel;
|
||||
args.source.slice = srcLayer;
|
||||
args.destination.level = dstLevel;
|
||||
args.destination.slice = dstLayer;
|
||||
|
||||
// TODO: The blit() call below always take the fast path.
|
||||
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blit/resolve");
|
||||
|
||||
dstTexture->extendLodRangeTo(dstLevel);
|
||||
}
|
||||
|
||||
void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
// If we're the in middle of a render pass, finish it.
|
||||
// This condition should only occur during copyFrame. It's okay to end the render pass because
|
||||
// we don't issue any other rendering commands.
|
||||
if (mContext->currentRenderPassEncoder) {
|
||||
[mContext->currentRenderPassEncoder endEncoding];
|
||||
mContext->currentRenderPassEncoder = nil;
|
||||
}
|
||||
|
||||
// Note: blitDEPRECATED is only used by Renderer::copyFrame
|
||||
// It is called between beginFrame and endFrame, but should never be called in the middle of
|
||||
// a render pass.
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blitDEPRECATED() cannot be invoked inside a render pass.");
|
||||
|
||||
auto srcTarget = handle_cast<MetalRenderTarget>(src);
|
||||
auto dstTarget = handle_cast<MetalRenderTarget>(dst);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
!(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)),
|
||||
"Blitting only supports COLOR0");
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
@@ -1327,54 +1429,28 @@ void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
t == MTLTextureType2DArray;
|
||||
};
|
||||
|
||||
// MetalBlitter supports blitting color and depth simultaneously, but for simplicitly we'll blit
|
||||
// them separately. In practice, Filament only ever blits a single buffer at a time anyway.
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
|
||||
if (any(buffers & TargetBufferFlags::COLOR_ALL)) {
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalRenderTarget::Attachment dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.source.texture = srcColorAttachment.getTexture();
|
||||
args.source.level = srcColorAttachment.level;
|
||||
args.source.slice = srcColorAttachment.layer;
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.source.color = srcColorAttachment.getTexture();
|
||||
args.destination.color = dstColorAttachment.getTexture();
|
||||
args.source.level = srcColorAttachment.level;
|
||||
args.destination.level = dstColorAttachment.level;
|
||||
args.source.slice = srcColorAttachment.layer;
|
||||
args.destination.slice = dstColorAttachment.layer;
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Color blit");
|
||||
}
|
||||
}
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.destination.texture = dstColorAttachment.getTexture();
|
||||
args.destination.level = dstColorAttachment.level;
|
||||
args.destination.slice = dstColorAttachment.layer;
|
||||
|
||||
if (any(buffers & TargetBufferFlags::DEPTH)) {
|
||||
MetalRenderTarget::Attachment srcDepthAttachment = srcTarget->getDepthAttachment();
|
||||
MetalRenderTarget::Attachment dstDepthAttachment = dstTarget->getDepthAttachment();
|
||||
|
||||
if (srcDepthAttachment && dstDepthAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcDepthAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstDepthAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.source.depth = srcDepthAttachment.getTexture();
|
||||
args.destination.depth = dstDepthAttachment.getTexture();
|
||||
args.source.level = srcDepthAttachment.level;
|
||||
args.destination.level = dstDepthAttachment.level;
|
||||
args.source.slice = srcDepthAttachment.layer;
|
||||
args.destination.slice = dstDepthAttachment.layer;
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Depth blit");
|
||||
}
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1512,7 +1588,7 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
stencilPixelFormat = stencilAttachment.getPixelFormat();
|
||||
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
|
||||
}
|
||||
MetalPipelineState pipelineState {
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = vertex,
|
||||
.fragmentFunction = fragment,
|
||||
.vertexDescription = primitive->vertexDescription,
|
||||
@@ -1530,13 +1606,13 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
.stencilAttachmentPixelFormat = stencilPixelFormat,
|
||||
.sampleCount = mContext->currentRenderTarget->getSamples(),
|
||||
.blendState = BlendState {
|
||||
.blendingEnabled = rs.hasBlending(),
|
||||
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
|
||||
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
|
||||
.sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB),
|
||||
.sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha),
|
||||
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
|
||||
.destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha),
|
||||
.destinationRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionDstRGB),
|
||||
.destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha)
|
||||
.sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha),
|
||||
.sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB),
|
||||
.blendingEnabled = rs.hasBlending(),
|
||||
},
|
||||
.colorWrite = rs.colorWrite
|
||||
};
|
||||
@@ -1835,6 +1911,21 @@ void MetalDriver::enumerateBoundBuffers(BufferObjectBinding bindingType,
|
||||
void MetalDriver::resetState(int) {
|
||||
}
|
||||
|
||||
void MetalDriver::runAtNextTick(const std::function<void()>& fn) noexcept {
|
||||
std::lock_guard<std::mutex> const lock(mTickOpsLock);
|
||||
mTickOps.push_back(fn);
|
||||
}
|
||||
|
||||
void MetalDriver::executeTickOps() noexcept {
|
||||
std::vector<std::function<void()>> ops;
|
||||
mTickOpsLock.lock();
|
||||
std::swap(ops, mTickOps);
|
||||
mTickOpsLock.unlock();
|
||||
for (const auto& f : ops) {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
// explicit instantiation of the Dispatcher
|
||||
template class ConcreteDispatcher<MetalDriver>;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "MetalUtils.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/trap.h>
|
||||
|
||||
#define NSERROR_CHECK(message) \
|
||||
|
||||
@@ -39,7 +39,15 @@ namespace filament {
|
||||
namespace backend {
|
||||
|
||||
static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
NSUInteger u = 0;
|
||||
NSUInteger u = MTLTextureUsageUnknown;
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
}
|
||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
// This is only needed because of the slowpath is MetalBlitter
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
@@ -49,9 +57,13 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
|
||||
// All textures can be blitted from, so they must have the UsageShaderRead flag.
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
if (any(usage & TextureUsage::BLIT_DST)) {
|
||||
// This is only needed because of the slowpath is MetalBlitter
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
}
|
||||
|
||||
return MTLTextureUsage(u);
|
||||
}
|
||||
@@ -233,13 +245,53 @@ void MetalSwapChain::present() {
|
||||
}
|
||||
}
|
||||
|
||||
void presentDrawable(bool presentFrame, void* user) {
|
||||
// CFBridgingRelease here is used to balance the CFBridgingRetain inside of acquireDrawable.
|
||||
id<CAMetalDrawable> drawable = (id<CAMetalDrawable>) CFBridgingRelease(user);
|
||||
if (presentFrame) {
|
||||
[drawable present];
|
||||
#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD
|
||||
#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1
|
||||
#endif
|
||||
|
||||
class PresentDrawableData {
|
||||
public:
|
||||
PresentDrawableData() = delete;
|
||||
PresentDrawableData(const PresentDrawableData&) = delete;
|
||||
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
|
||||
|
||||
static PresentDrawableData* create(id<CAMetalDrawable> drawable, MetalDriver* driver) {
|
||||
assert_invariant(driver);
|
||||
return new PresentDrawableData(drawable, driver);
|
||||
}
|
||||
// The drawable will be released here when the "drawable" variable goes out of scope.
|
||||
|
||||
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
|
||||
if (shouldPresent) {
|
||||
[that->mDrawable present];
|
||||
}
|
||||
|
||||
#if FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD == 1
|
||||
// mDrawable is acquired on the driver thread. Typically, we would release this object on
|
||||
// the same thread, but after receiving consistent crash reports from within
|
||||
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); });
|
||||
#else
|
||||
that->mDriver->runAtNextTick([that]() { cleanupAndDestroy(that); });
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
PresentDrawableData(id<CAMetalDrawable> drawable, MetalDriver* driver)
|
||||
: mDrawable(drawable), mDriver(driver) {}
|
||||
|
||||
static void cleanupAndDestroy(PresentDrawableData *that) {
|
||||
that->mDrawable = nil;
|
||||
that->mDriver = nullptr;
|
||||
delete that;
|
||||
}
|
||||
|
||||
id<CAMetalDrawable> mDrawable;
|
||||
MetalDriver* mDriver = nullptr;
|
||||
};
|
||||
|
||||
void presentDrawable(bool presentFrame, void* user) {
|
||||
auto* presentDrawableData = static_cast<PresentDrawableData*>(user);
|
||||
PresentDrawableData::maybePresentAndDestroyAsync(presentDrawableData, presentFrame);
|
||||
}
|
||||
|
||||
void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
@@ -248,19 +300,16 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
}
|
||||
|
||||
assert_invariant(drawable);
|
||||
FrameScheduledCallback callback = frameScheduledCallback;
|
||||
// This block strongly captures drawable to keep it alive until the handler executes.
|
||||
// We cannot simply reference this->drawable inside the block because the block would then only
|
||||
// capture the _this_ pointer (MetalSwapChain*) instead of the drawable.
|
||||
id<CAMetalDrawable> d = drawable;
|
||||
|
||||
// Destroy this by calling maybePresentAndDestroyAsync() later.
|
||||
auto* presentData = PresentDrawableData::create(drawable, context.driver);
|
||||
|
||||
FrameScheduledCallback userCallback = frameScheduledCallback;
|
||||
void* userData = frameScheduledUserData;
|
||||
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
// CFBridgingRetain is used here to give the drawable a +1 retain count before
|
||||
// casting it to a void*.
|
||||
PresentCallable callable(presentDrawable, (void*) CFBridgingRetain(d));
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
callback(callable, userData);
|
||||
});
|
||||
PresentCallable callable(presentDrawable, static_cast<void*>(presentData));
|
||||
userCallback(callable, userData);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -756,6 +805,7 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
#endif
|
||||
|
||||
id<MTLTexture> stagingTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
// FIXME? Why is this not just `MTLRegion sourceRegion = region;` ?
|
||||
MTLRegion sourceRegion = MTLRegionMake3D(0, 0, 0,
|
||||
region.size.width, region.size.height, region.size.depth);
|
||||
[stagingTexture replaceRegion:sourceRegion
|
||||
@@ -782,16 +832,16 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
slices:NSMakeRange(0, slices)];
|
||||
}
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.level = 0;
|
||||
args.source.slice = 0;
|
||||
args.source.region = sourceRegion;
|
||||
args.source.texture = stagingTexture;
|
||||
args.destination.level = level;
|
||||
args.destination.slice = slice;
|
||||
args.destination.region = region;
|
||||
args.source.color = stagingTexture;
|
||||
args.destination.color = destinationTexture;
|
||||
args.destination.texture = destinationTexture;
|
||||
context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit");
|
||||
}
|
||||
|
||||
|
||||
@@ -103,10 +103,19 @@ void MetalShaderCompiler::terminate() noexcept {
|
||||
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;
|
||||
// When options is nil, Metal uses the most recent language version available.
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
options:options
|
||||
error:&error];
|
||||
if (library == nil) {
|
||||
if (error) {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#include "MetalEnums.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
|
||||
@@ -182,6 +182,10 @@ bool NoopDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
@@ -252,10 +256,6 @@ void NoopDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
||||
|
||||
void NoopDriver::generateMipmaps(Handle<HwTexture> th) { }
|
||||
|
||||
bool NoopDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void NoopDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
BufferDescriptor&& data) {
|
||||
scheduleDestroy(std::move(data));
|
||||
@@ -322,12 +322,23 @@ void NoopDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void NoopDriver::blit(TargetBufferFlags buffers,
|
||||
void NoopDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
}
|
||||
|
||||
void NoopDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
}
|
||||
|
||||
void NoopDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
}
|
||||
|
||||
void NoopDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t instanceCount) {
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
||||
#endif
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
if (ext.EXT_clip_control) {
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
||||
@@ -281,6 +282,7 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ext.EXT_clip_cull_distance) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
@@ -309,7 +311,9 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
# ifdef BACKEND_OPENGL_VERSION_GL
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
|
||||
# elif defined(GL_EXT_disjoint_timer_query)
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# ifndef __EMSCRIPTEN__
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# endif
|
||||
# endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
// core in ES 3.0 and GL 4.3
|
||||
@@ -321,6 +325,7 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
# ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
# ifndef __EMSCRIPTEN__
|
||||
if (UTILS_UNLIKELY(major == 2)) {
|
||||
// Runtime OpenGL version is ES 2.x
|
||||
if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
|
||||
@@ -348,6 +353,7 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
|
||||
}
|
||||
# endif // __EMSCRIPTEN__
|
||||
# endif // IOS
|
||||
#else
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
|
||||
@@ -421,7 +427,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
if (strstr(renderer, "Mali-T")) {
|
||||
bugs->disable_glFlush = true;
|
||||
bugs->disable_shared_context_draws = true;
|
||||
bugs->texture_external_needs_rebind = true;
|
||||
// We have not verified that timer queries work on Mali-T, so we disable to be safe.
|
||||
bugs->dont_use_timer_query = true;
|
||||
}
|
||||
@@ -476,7 +481,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
// (that should be regardless of ANGLE, but we should double-check)
|
||||
bugs->split_easu = true;
|
||||
}
|
||||
// TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
|
||||
}
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
@@ -581,17 +585,23 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
// figure out and initialize the extensions we need
|
||||
using namespace std::literals;
|
||||
ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
|
||||
#endif
|
||||
ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
|
||||
ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
|
||||
ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
#endif
|
||||
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
|
||||
ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
|
||||
#endif
|
||||
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_texture_compression_etc2 = true;
|
||||
#endif
|
||||
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
||||
|
||||
@@ -222,10 +222,6 @@ public:
|
||||
// Some drivers have gl state issues when drawing from shared contexts
|
||||
bool disable_shared_context_draws;
|
||||
|
||||
// Some drivers require the GL_TEXTURE_EXTERNAL_OES target to be bound when
|
||||
// the texture image changes, even if it's already bound to that texture
|
||||
bool texture_external_needs_rebind;
|
||||
|
||||
// Some web browsers seem to immediately clear the default framebuffer when calling
|
||||
// glInvalidateFramebuffer with WebGL 2.0
|
||||
bool disable_invalidate_framebuffer;
|
||||
@@ -433,9 +429,6 @@ private:
|
||||
{ bugs.disable_shared_context_draws,
|
||||
"disable_shared_context_draws",
|
||||
""},
|
||||
{ bugs.texture_external_needs_rebind,
|
||||
"texture_external_needs_rebind",
|
||||
""},
|
||||
{ bugs.disable_invalidate_framebuffer,
|
||||
"disable_invalidate_framebuffer",
|
||||
""},
|
||||
@@ -711,7 +704,7 @@ void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId, size_t
|
||||
update_state(state.textures.units[unit].targets[targetIndex].texture_id, texId, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(target, texId);
|
||||
}, (target == GL_TEXTURE_EXTERNAL_OES) && bugs.texture_external_needs_rebind);
|
||||
}, target == GL_TEXTURE_EXTERNAL_OES);
|
||||
}
|
||||
|
||||
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept {
|
||||
|
||||
@@ -147,9 +147,9 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
#endif
|
||||
|
||||
size_t const defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
Platform::DriverConfig validConfig{ driverConfig };
|
||||
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||
OpenGLDriver* const driver = new OpenGLDriver(ec, validConfig);
|
||||
OpenGLDriver* const driver = new(std::nothrow) OpenGLDriver(ec, validConfig);
|
||||
return driver;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,8 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
||||
mContext(),
|
||||
mShaderCompilerService(*this),
|
||||
mHandleAllocator("Handles", driverConfig.handleArenaSize),
|
||||
mSamplerMap(32) {
|
||||
mSamplerMap(32),
|
||||
mDriverConfig(driverConfig) {
|
||||
|
||||
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
|
||||
|
||||
@@ -262,7 +263,12 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.targetIndex);
|
||||
}
|
||||
|
||||
void OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
if (UTILS_UNLIKELY(!p->isValid())) {
|
||||
// If the program is not valid, we can't call use().
|
||||
return false;
|
||||
}
|
||||
|
||||
// set-up textures and samplers in the proper TMUs (as specified in setSamplers)
|
||||
p->use(this, mContext);
|
||||
|
||||
@@ -277,6 +283,7 @@ void OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
|
||||
p->setRec709ColorSpace(mRec709OutputColorspace);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -531,7 +538,7 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
// Here we patch the specification constants to enable or not the rec709 output
|
||||
// Here we patch the specialization constants to enable or not the rec709 output
|
||||
// color space emulation in this program. Obviously, the backend shouldn't know about
|
||||
// specific spec-constants, so we need to handle failures gracefully. This cannot be
|
||||
// done at Material creation time because only the backend has access to
|
||||
@@ -1058,6 +1065,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
} else
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_multisampled_render_to_texture
|
||||
// EXT_multisampled_render_to_texture only support GL_COLOR_ATTACHMENT0
|
||||
if (!attachmentTypeNotSupportedByMSRTT && (t->depth <= 1)
|
||||
@@ -1070,7 +1078,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
// This extension only exists on OpenGL ES.
|
||||
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
|
||||
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glext::glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
|
||||
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
|
||||
attachment, target, t->gl.id, binfo.level, rt->gl.samples);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment,
|
||||
@@ -1078,7 +1086,8 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
} else
|
||||
#endif
|
||||
#endif // GL_EXT_multisampled_render_to_texture
|
||||
#endif // __EMSCRIPTEN__
|
||||
if (!any(t->usage & TextureUsage::SAMPLEABLE) && t->samples > 1) {
|
||||
assert_invariant(rt->gl.samples > 1);
|
||||
assert_invariant(glIsRenderbuffer(t->gl.id));
|
||||
@@ -1177,6 +1186,7 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32
|
||||
uint32_t height, uint8_t samples) const noexcept {
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
|
||||
if (samples > 1) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_multisampled_render_to_texture
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_multisampled_render_to_texture ||
|
||||
@@ -1184,7 +1194,8 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32
|
||||
glext::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER,
|
||||
samples, internalformat, width, height);
|
||||
} else
|
||||
#endif
|
||||
#endif // GL_EXT_multisampled_render_to_texture
|
||||
#endif // __EMSCRIPTEN__
|
||||
{
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
|
||||
@@ -1900,6 +1911,10 @@ bool OpenGLDriver::isParallelShaderCompileSupported() {
|
||||
return mShaderCompilerService.isParallelShaderCompileSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -1969,6 +1984,13 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
|
||||
GLSwapChain* scRead = handle_cast<GLSwapChain*>(schRead);
|
||||
mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain);
|
||||
mCurrentDrawSwapChain = scDraw;
|
||||
|
||||
// From the GL spec for glViewport and glScissor:
|
||||
// When a GL context is first attached to a window, width and height are set to the
|
||||
// dimensions of that window.
|
||||
// So basically, our viewport/scissor can be reset to "something" here.
|
||||
mContext.state.window.viewport = {};
|
||||
mContext.state.window.scissor = {};
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -2214,8 +2236,7 @@ void OpenGLDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint
|
||||
t->gl.baseLevel = (int8_t)minLevel;
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
|
||||
|
||||
t->gl
|
||||
.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
|
||||
t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
|
||||
}
|
||||
#endif
|
||||
@@ -2265,16 +2286,13 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
bool OpenGLDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
PixelBufferDescriptor&& p) {
|
||||
auto& gl = mContext;
|
||||
|
||||
assert_invariant(t != nullptr);
|
||||
assert_invariant(xoffset + width <= std::max(1u, t->width >> level));
|
||||
assert_invariant(yoffset + height <= std::max(1u, t->height >> level));
|
||||
assert_invariant(t->samples <= 1);
|
||||
@@ -2973,16 +2991,18 @@ GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
#endif
|
||||
|
||||
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_debug_marker) {
|
||||
glInsertEventMarkerEXT(GLsizei(len ? len : strlen(string)), string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
@@ -2995,9 +3015,11 @@ void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::popGroupMarker(int) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
@@ -3010,6 +3032,7 @@ void OpenGLDriver::popGroupMarker(int) {
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::startCapture(int) {
|
||||
@@ -3425,75 +3448,230 @@ void OpenGLDriver::clearWithRasterPipe(TargetBufferFlags clearFlags,
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::blit(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
void OpenGLDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
DEBUG_MARKER()
|
||||
GLTexture const* const s = handle_cast<GLTexture*>(src);
|
||||
GLTexture const* const d = handle_cast<GLTexture*>(dst);
|
||||
assert_invariant(s);
|
||||
assert_invariant(d);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
d->width == s->width && d->height == s->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(s->samples > 1 && d->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+s->samples, +d->samples);
|
||||
|
||||
blit( dst, dstLevel, dstLayer, {},
|
||||
src, srcLevel, srcLayer, {},
|
||||
{ d->width, d->height });
|
||||
}
|
||||
|
||||
void OpenGLDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, uint2 srcOrigin,
|
||||
uint2 size) {
|
||||
DEBUG_MARKER()
|
||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||
assert_invariant(!gl.isES2());
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLbitfield const mask = getAttachmentBitfield(buffers);
|
||||
if (mask) {
|
||||
GLenum glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR;
|
||||
if (mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) {
|
||||
// GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or
|
||||
// GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.
|
||||
glFilterMode = GL_NEAREST;
|
||||
}
|
||||
|
||||
// note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar
|
||||
// buffer -- this should produce the same output that if we copied from the resolved
|
||||
// texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this
|
||||
// is an emulation of that. We cannot use the resolved texture easily because it's not
|
||||
// actually attached to the RenderTarget. Another implementation would be to do a
|
||||
// reverse-resolve, but that wouldn't buy us anything.
|
||||
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
||||
GLRenderTarget const* d = handle_cast<GLRenderTarget const*>(dst);
|
||||
GLTexture* d = handle_cast<GLTexture*>(dst);
|
||||
GLTexture* s = handle_cast<GLTexture*>(src);
|
||||
assert_invariant(d);
|
||||
assert_invariant(s);
|
||||
|
||||
// blit operations are only supported from RenderTargets that have only COLOR0 (or
|
||||
// no color buffer at all)
|
||||
assert_invariant(
|
||||
!(s->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
ASSERT_PRECONDITION_NON_FATAL(any(d->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
assert_invariant(
|
||||
!(d->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
ASSERT_PRECONDITION_NON_FATAL(any(s->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
// With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS
|
||||
// for the draw buffer is greater than zero. This works with OpenGL, so we want to
|
||||
// make sure to catch this scenario.
|
||||
assert_invariant(d->gl.samples <= 1);
|
||||
ASSERT_PRECONDITION_NON_FATAL(s->format == d->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and the formats of draw and read buffers are not identical.
|
||||
// However, it's not well-defined in the spec what "format" means. So it's difficult
|
||||
// to have an assert here -- especially when dealing with the default framebuffer
|
||||
enum class AttachmentType : GLenum {
|
||||
COLOR = GL_COLOR_ATTACHMENT0,
|
||||
DEPTH = GL_DEPTH_ATTACHMENT,
|
||||
STENCIL = GL_STENCIL_ATTACHMENT,
|
||||
DEPTH_STENCIL = GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
};
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and (...) the source and destination rectangles are not defined with the
|
||||
// same (X0, Y0) and (X1, Y1) bounds.
|
||||
auto getFormatType = [](TextureFormat format) -> AttachmentType {
|
||||
bool const depth = isDepthFormat(format);
|
||||
bool const stencil = isStencilFormat(format);
|
||||
if (depth && stencil) return AttachmentType::DEPTH_STENCIL;
|
||||
if (depth) return AttachmentType::DEPTH;
|
||||
if (stencil) return AttachmentType::STENCIL;
|
||||
return AttachmentType::COLOR;
|
||||
};
|
||||
|
||||
// Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what
|
||||
// happens when blitting from an "implicit" resolve render target (does it work?), so
|
||||
// to ere on the safe side, we don't allow it.
|
||||
if (s->gl.samples > 1) {
|
||||
assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect)));
|
||||
}
|
||||
AttachmentType const type = getFormatType(d->format);
|
||||
assert_invariant(type == getFormatType(s->format));
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
|
||||
// GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or
|
||||
// GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.
|
||||
GLbitfield mask = {};
|
||||
switch (type) {
|
||||
case AttachmentType::COLOR:
|
||||
mask = GL_COLOR_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::DEPTH:
|
||||
mask = GL_DEPTH_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::STENCIL:
|
||||
mask = GL_STENCIL_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::DEPTH_STENCIL:
|
||||
mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
break;
|
||||
};
|
||||
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
GLuint fbo[2] = {};
|
||||
glGenFramebuffers(2, fbo);
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
|
||||
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
|
||||
mask, glFilterMode);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[0]);
|
||||
switch (d->target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_2D, d->gl.id, dstLevel);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_RENDERBUFFER, d->gl.id);
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + dstLayer, d->gl.id, dstLevel);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
case SamplerType::SAMPLER_3D:
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
d->gl.id, dstLevel, dstLayer);
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
|
||||
switch (s->target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
if (any(s->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_2D, s->gl.id, srcLevel);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_RENDERBUFFER, s->gl.id);
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + srcLayer, s->gl.id, srcLevel);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
case SamplerType::SAMPLER_3D:
|
||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
s->gl.id, srcLevel, srcLayer);
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcOrigin.x, srcOrigin.y, srcOrigin.x + size.x, srcOrigin.y + size.y,
|
||||
dstOrigin.x, dstOrigin.y, dstOrigin.x + size.x, dstOrigin.y + size.y,
|
||||
mask, GL_NEAREST);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
|
||||
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
|
||||
// In a sense, blitting to a texture level is similar to calling setTextureData on it; in
|
||||
// both cases, we update the base/max LOD to give shaders access to levels as they become
|
||||
// available. Note that this can only expand the LOD range (never shrink it), and that
|
||||
// users can override this range by calling setMinMaxLevels().
|
||||
updateTextureLodRange(d, int8_t(dstLevel));
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
|
||||
// Note: blitDEPRECATED is only used by Renderer::copyFrame
|
||||
|
||||
DEBUG_MARKER()
|
||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||
assert_invariant(!gl.isES2());
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
|
||||
GLenum const glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR;
|
||||
|
||||
// note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar
|
||||
// buffer -- this should produce the same output that if we copied from the resolved
|
||||
// texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this
|
||||
// is an emulation of that. We cannot use the resolved texture easily because it's not
|
||||
// actually attached to the RenderTarget. Another implementation would be to do a
|
||||
// reverse-resolve, but that wouldn't buy us anything.
|
||||
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
||||
GLRenderTarget const* d = handle_cast<GLRenderTarget const*>(dst);
|
||||
|
||||
// With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS
|
||||
// for the draw buffer is greater than zero. This works with OpenGL, so we want to
|
||||
// make sure to catch this scenario.
|
||||
assert_invariant(d->gl.samples <= 1);
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and the formats of draw and read buffers are not identical.
|
||||
// However, it's not well-defined in the spec what "format" means. So it's difficult
|
||||
// to have an assert here -- especially when dealing with the default framebuffer
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and (...) the source and destination rectangles are not defined with the
|
||||
// same (X0, Y0) and (X1, Y1) bounds.
|
||||
|
||||
// Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what
|
||||
// happens when blitting from an "implicit" resolve render target (does it work?), so
|
||||
// to ere on the safe side, we don't allow it.
|
||||
if (s->gl.samples > 1) {
|
||||
assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect)));
|
||||
}
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
|
||||
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
|
||||
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
|
||||
GL_COLOR_BUFFER_BIT, glFilterMode);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3524,18 +3702,17 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(state.program);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
|
||||
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
|
||||
// during the draw call when the program is invalid. The shader compile error has already been
|
||||
// dumped to the console at this point, so it's fine to simply return early.
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
|
||||
bool const success = useProgram(p);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
|
||||
// is invalid. The shader compile error has already been dumped to the console at this
|
||||
// point, so it's fine to simply return early.
|
||||
return;
|
||||
}
|
||||
|
||||
useProgram(p);
|
||||
|
||||
GLRenderPrimitive* rp = handle_cast<GLRenderPrimitive *>(rph);
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
|
||||
// Gracefully do nothing if the render primitive has not been set up.
|
||||
VertexBufferHandle vb = rp->gl.vertexBufferWithObjects;
|
||||
@@ -3546,7 +3723,7 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
|
||||
// If necessary, mutate the bindings in the VAO.
|
||||
const GLVertexBuffer* glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
GLVertexBuffer const* const glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
if (UTILS_UNLIKELY(rp->gl.vertexBufferVersion != glvb->bufferObjectsVersion)) {
|
||||
updateVertexArrayObject(rp, glvb);
|
||||
}
|
||||
@@ -3580,17 +3757,16 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(program);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(program);
|
||||
|
||||
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
|
||||
// during the draw call when the program is invalid. The shader compile error has already been
|
||||
// dumped to the console at this point, so it's fine to simply return early.
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
|
||||
bool const success = useProgram(p);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
|
||||
// is invalid. The shader compile error has already been dumped to the console at this
|
||||
// point, so it's fine to simply return early.
|
||||
return;
|
||||
}
|
||||
|
||||
useProgram(p);
|
||||
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
|
||||
@@ -318,7 +318,7 @@ private:
|
||||
|
||||
void bindTexture(GLuint unit, GLTexture const* t) noexcept;
|
||||
void bindSampler(GLuint unit, GLuint sampler) noexcept;
|
||||
inline void useProgram(OpenGLProgram* p) noexcept;
|
||||
inline bool useProgram(OpenGLProgram* p) noexcept;
|
||||
|
||||
enum class ResolveAction { LOAD, STORE };
|
||||
void resolvePass(ResolveAction action, GLRenderTarget const* rt,
|
||||
@@ -400,6 +400,9 @@ private:
|
||||
// timer query implementation
|
||||
OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr;
|
||||
|
||||
const Platform::DriverConfig mDriverConfig;
|
||||
Platform::DriverConfig const& getDriverConfig() const noexcept { return mDriverConfig; }
|
||||
|
||||
// for ES2 sRGB support
|
||||
GLSwapChain* mCurrentDrawSwapChain = nullptr;
|
||||
bool mRec709OutputColorspace = false;
|
||||
|
||||
@@ -129,7 +129,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
#endif
|
||||
{
|
||||
// ES2 initialization of (fake) UBOs
|
||||
UniformsRecord* const uniformsRecords = new UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint binding = 0, n = Program::UNIFORM_BINDING_COUNT; binding < n; binding++) {
|
||||
Program::UniformInfo& uniforms = lazyInitializationData.bindingUniformInfo[binding];
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
// - the content of any bound sampler buffer has changed
|
||||
// ... since last time we used this program
|
||||
|
||||
// turns out the former might be relatively cheap to check, the later requires
|
||||
// Turns out the former might be relatively cheap to check, the latter requires
|
||||
// a bit less. Compared to what updateSamplers() actually does, which is
|
||||
// pretty little, I'm not sure if we'll get ahead.
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
@@ -141,20 +140,43 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept
|
||||
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
|
||||
: mDriver(driver),
|
||||
mBlobCache(driver.getContext()),
|
||||
mCallbackManager(driver),
|
||||
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
|
||||
mCallbackManager(driver) {
|
||||
}
|
||||
|
||||
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
|
||||
|
||||
bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept {
|
||||
return KHR_parallel_shader_compile || mShaderCompilerThreadCount;
|
||||
assert_invariant(mMode != Mode::UNDEFINED);
|
||||
return mMode != Mode::SYNCHRONOUS;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::init() noexcept {
|
||||
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
|
||||
// friendly.
|
||||
if (!KHR_parallel_shader_compile) {
|
||||
if (UTILS_UNLIKELY(mDriver.getDriverConfig().disableParallelShaderCompile)) {
|
||||
// user disabled parallel shader compile
|
||||
mMode = Mode::SYNCHRONOUS;
|
||||
return;
|
||||
}
|
||||
|
||||
// Here we decide which mode we'll be using. We always prefer our own thread-pool if
|
||||
// that mode is available because, we have no control on how the compilation queues are
|
||||
// handled if done by the driver (so at the very least we'd need to decode this per-driver).
|
||||
// In theory, using Mode::ASYNCHRONOUS (a.k.a. KHR_parallel_shader_compile) could be more
|
||||
// efficient, since it doesn't require shared contexts.
|
||||
// In practice, we already know that with ANGLE, Mode::ASYNCHRONOUS can cause very long
|
||||
// pauses at glDraw() time in the situation where glLinkProgram() has been emitted, but has
|
||||
// other programs ahead of it in ANGLE's queue.
|
||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||
// our thread-pool if possible
|
||||
mMode = Mode::THREAD_POOL;
|
||||
} else if (mDriver.getContext().ext.KHR_parallel_shader_compile) {
|
||||
// if not, async shader compilation and link if the driver supports it
|
||||
mMode = Mode::ASYNCHRONOUS;
|
||||
} else {
|
||||
// fallback to synchronous shader compilation
|
||||
mMode = Mode::SYNCHRONOUS;
|
||||
}
|
||||
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
||||
// also glProgramBinary blocks if other threads are compiling.
|
||||
// - on Mali shader compilation can be multi-threaded, but program linking happens on
|
||||
@@ -164,37 +186,42 @@ void ShaderCompilerService::init() noexcept {
|
||||
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
|
||||
// a shared context, so parallel shader compilation yields no benefit.
|
||||
// - on windows/linux we could use more threads, tbd.
|
||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||
// safest choice that avoids priority inversions.
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
auto const& renderer = mDriver.getContext().state.renderer;
|
||||
if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) {
|
||||
// The PowerVR driver support parallel shader compilation well, so we use 2
|
||||
// threads, we can use lower priority threads here because urgent compilations
|
||||
// will most likely happen on the main gl thread. Using too many thread can
|
||||
// increase memory pressure significantly.
|
||||
poolSize = 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
}
|
||||
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||
// safest choice that avoids priority inversions.
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
JobSystem::setThreadPriority(priority);
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
auto const& renderer = mDriver.getContext().state.renderer;
|
||||
// Some drivers support parallel shader compilation well, so we use N
|
||||
// threads, we can use lower priority threads here because urgent compilations
|
||||
// will most likely happen on the main gl thread. Using too many thread can
|
||||
// increase memory pressure significantly.
|
||||
if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) {
|
||||
// For PowerVR, it's unclear what the cost of extra shared contexts is, so we only
|
||||
// use 2 to be safe.
|
||||
poolSize = 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
} else if (UTILS_UNLIKELY(strstr(renderer, "ANGLE"))) {
|
||||
// Angle shared contexts are not expensive once we have two.
|
||||
poolSize = (std::thread::hardware_concurrency() + 1) / 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
}
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
JobSystem::setThreadPriority(priority);
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +255,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
if (mShaderCompilerThreadCount) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// queue a compile job
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
@@ -254,8 +281,6 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||
programData.program = glProgram;
|
||||
|
||||
token->gl.program = programData.program;
|
||||
|
||||
// we don't need to check for success here, it'll be done on the
|
||||
// main thread side.
|
||||
token->set(programData);
|
||||
@@ -279,7 +304,8 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->shaderSourceCode);
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
if (KHR_parallel_shader_compile) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// don't attempt to link this program if all shaders are not done compiling
|
||||
GLint status;
|
||||
if (token->gl.program) {
|
||||
@@ -303,7 +329,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
if (KHR_parallel_shader_compile) {
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// wait until the link finishes...
|
||||
return false;
|
||||
}
|
||||
@@ -337,14 +363,14 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
return program;
|
||||
}
|
||||
|
||||
/* static*/ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
bool canceled = token->compiler.cancelTickOp(token);
|
||||
bool const canceled = token->compiler.cancelTickOp(token);
|
||||
|
||||
if (token->compiler.mShaderCompilerThreadCount) {
|
||||
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
if (!job) {
|
||||
// The job is being executed right now. We need to wait for it to finish to avoid a
|
||||
@@ -378,7 +404,7 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
|
||||
void ShaderCompilerService::tick() {
|
||||
// we don't need to run executeTickOps() if we're using the thread-pool
|
||||
if (UTILS_UNLIKELY(!mShaderCompilerThreadCount)) {
|
||||
if (UTILS_UNLIKELY(mMode != Mode::THREAD_POOL)) {
|
||||
executeTickOps();
|
||||
}
|
||||
}
|
||||
@@ -403,7 +429,7 @@ void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) n
|
||||
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (!token->gl.program) {
|
||||
if (mShaderCompilerThreadCount) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// we need this program right now, remove it from the queue
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
@@ -419,7 +445,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
// Block until we get the program from the pool. Generally this wouldn't block
|
||||
// because we just compiled the program above, when executing job.
|
||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||
} else if (KHR_parallel_shader_compile) {
|
||||
} else if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// we force the program link -- which might stall, either here or below in
|
||||
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||
token->compiler.cancelTickOp(token);
|
||||
@@ -527,7 +553,8 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
// 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 specification constants and the packing functions
|
||||
// split shader source, so we can insert the specialization constants and the packing
|
||||
// functions
|
||||
auto const [prolog, body] = splitShaderSource(source);
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
|
||||
@@ -95,19 +95,42 @@ public:
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
|
||||
private:
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
}
|
||||
std::function<bool(Job const& job)> fn;
|
||||
CallbackHandler* handler = nullptr;
|
||||
void* user = nullptr;
|
||||
CallbackHandler::Callback callback{};
|
||||
};
|
||||
|
||||
enum class Mode {
|
||||
UNDEFINED, // init() has not been called yet.
|
||||
SYNCHRONOUS, // synchronous shader compilation
|
||||
THREAD_POOL, // asynchronous shader compilation using a thread-pool (most common)
|
||||
ASYNCHRONOUS // asynchronous shader compilation using KHR_parallel_shader_compile
|
||||
};
|
||||
|
||||
OpenGLDriver& mDriver;
|
||||
OpenGLBlobCache mBlobCache;
|
||||
CallbackManager mCallbackManager;
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
|
||||
const bool KHR_parallel_shader_compile;
|
||||
uint32_t mShaderCompilerThreadCount = 0u;
|
||||
Mode mMode = Mode::UNDEFINED; // valid after init() is called
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
|
||||
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||
|
||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||
|
||||
static void compileShaders(
|
||||
static void compileShaders(
|
||||
OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
@@ -127,27 +150,11 @@ private:
|
||||
|
||||
static bool checkProgramStatus(program_token_t const& token) noexcept;
|
||||
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
}
|
||||
std::function<bool(Job const& job)> fn;
|
||||
CallbackHandler* handler = nullptr;
|
||||
void* user = nullptr;
|
||||
CallbackHandler::Callback callback{};
|
||||
};
|
||||
|
||||
void runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept;
|
||||
void executeTickOps() noexcept;
|
||||
bool cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -28,6 +28,7 @@ static void getProcAddress(T& pfn, const char* name) noexcept {
|
||||
}
|
||||
|
||||
namespace glext {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_OES_EGL_image
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
|
||||
#endif
|
||||
@@ -72,10 +73,11 @@ PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
// use getProcAddress for ES3.1 and above entry points.
|
||||
PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
#endif
|
||||
|
||||
static std::once_flag sGlExtInitialized;
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
void importGLESExtensionsEntryPoints() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::call_once(sGlExtInitialized, +[]() {
|
||||
#ifdef GL_OES_EGL_image
|
||||
getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
|
||||
@@ -119,6 +121,7 @@ void importGLESExtensionsEntryPoints() {
|
||||
getProcAddress(glDispatchCompute, "glDispatchCompute");
|
||||
#endif
|
||||
});
|
||||
#endif // __EMSCRIPTEN__
|
||||
}
|
||||
|
||||
} // namespace glext
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace glext {
|
||||
// it is currently called from PlatformEGL.
|
||||
void importGLESExtensionsEntryPoints();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_OES_EGL_image
|
||||
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
|
||||
#endif
|
||||
@@ -153,6 +154,7 @@ extern PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
|
||||
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
#endif
|
||||
#endif // __EMSCRIPTEN__
|
||||
} // namespace glext
|
||||
|
||||
using namespace glext;
|
||||
|
||||
@@ -293,20 +293,6 @@ io::ostream& operator<<(io::ostream& out, TextureFormat format) {
|
||||
return out;
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, TextureUsage usage) {
|
||||
switch (usage) {
|
||||
CASE(TextureUsage, NONE)
|
||||
CASE(TextureUsage, DEFAULT)
|
||||
CASE(TextureUsage, COLOR_ATTACHMENT)
|
||||
CASE(TextureUsage, DEPTH_ATTACHMENT)
|
||||
CASE(TextureUsage, STENCIL_ATTACHMENT)
|
||||
CASE(TextureUsage, UPLOADABLE)
|
||||
CASE(TextureUsage, SAMPLEABLE)
|
||||
CASE(TextureUsage, SUBPASS_INPUT)
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, TextureCubemapFace face) {
|
||||
switch (face) {
|
||||
CASE(TextureCubemapFace, NEGATIVE_X)
|
||||
@@ -469,7 +455,7 @@ io::ostream& operator<<(io::ostream& out, filament::backend::Viewport const& vie
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, TargetBufferFlags flags) {
|
||||
// TODO: implement decoding of enum
|
||||
out << uint8_t(flags);
|
||||
out << uint32_t(flags);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,22 +38,15 @@ using ImgUtil = VulkanImageUtility;
|
||||
namespace {
|
||||
|
||||
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
|
||||
const VkExtent2D srcExtent, VulkanAttachment src, VulkanAttachment dst,
|
||||
VulkanAttachment src, VulkanAttachment dst,
|
||||
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
const VkImageBlit blitRegions[1] = {{
|
||||
.srcSubresource = {aspect, src.level, src.layer, 1},
|
||||
.srcOffsets = {srcRect[0], srcRect[1]},
|
||||
.dstSubresource = {aspect, dst.level, dst.layer, 1},
|
||||
.dstOffsets = {dstRect[0], dstRect[1]},
|
||||
}};
|
||||
|
||||
const VkImageResolve resolveRegions[1] = {{
|
||||
.srcSubresource = {aspect, src.level, src.layer, 1},
|
||||
.srcOffset = srcRect[0],
|
||||
.dstSubresource = {aspect, dst.level, dst.layer, 1},
|
||||
.dstOffset = dstRect[0],
|
||||
.extent = {srcExtent.width, srcExtent.height, 1},
|
||||
}};
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< " layout=" << src.getLayout()
|
||||
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
}
|
||||
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
@@ -71,6 +64,36 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
|
||||
const VkImageBlit blitRegions[1] = {{
|
||||
.srcSubresource = { aspect, src.level, src.layer, 1 },
|
||||
.srcOffsets = { srcRect[0], srcRect[1] },
|
||||
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
|
||||
.dstOffsets = { dstRect[0], dstRect[1] },
|
||||
}};
|
||||
vkCmdBlitImage(cmdbuffer,
|
||||
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);
|
||||
}
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
|
||||
}
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -78,25 +101,41 @@ 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,
|
||||
};
|
||||
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
|
||||
if (src.texture->samples > 1 && dst.texture->samples == 1) {
|
||||
assert_invariant(
|
||||
aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported.");
|
||||
vkCmdResolveImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, resolveRegions);
|
||||
} else {
|
||||
vkCmdBlitImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, blitRegions, filter);
|
||||
}
|
||||
assert_invariant(
|
||||
aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported.");
|
||||
const VkImageResolve resolveRegions[1] = {{
|
||||
.srcSubresource = { aspect, src.level, src.layer, 1 },
|
||||
.srcOffset = { 0, 0 },
|
||||
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
|
||||
.dstOffset = { 0, 0 },
|
||||
.extent = { src.getExtent2D().width, src.getExtent2D().height, 1 },
|
||||
}};
|
||||
vkCmdResolveImage(cmdbuffer,
|
||||
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);
|
||||
@@ -115,52 +154,22 @@ struct BlitterUniforms {
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
VulkanBlitter::VulkanBlitter(VulkanStagePool& stagePool, VulkanPipelineCache& pipelineCache,
|
||||
VulkanFboCache& fboCache, VulkanSamplerCache& samplerCache) noexcept
|
||||
: mStagePool(stagePool), mPipelineCache(pipelineCache), mFramebufferCache(fboCache),
|
||||
mSamplerCache(samplerCache) {}
|
||||
VulkanBlitter::VulkanBlitter() noexcept = default;
|
||||
|
||||
void VulkanBlitter::initialize(VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
VmaAllocator allocator, VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept {
|
||||
VmaAllocator allocator, VulkanCommands* commands) noexcept {
|
||||
mPhysicalDevice = physicalDevice;
|
||||
mDevice = device;
|
||||
mAllocator = allocator;
|
||||
mCommands = commands;
|
||||
mEmptyTexture = emptyTexture;
|
||||
}
|
||||
|
||||
void VulkanBlitter::blitColor(BlitArgs args) {
|
||||
const VulkanAttachment src = args.srcTarget->getColor(args.targetIndex);
|
||||
const VulkanAttachment dst = args.dstTarget->getColor(0);
|
||||
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
||||
"Source format is not blittable %d", src.getFormat())) {
|
||||
return;
|
||||
}
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
||||
"Destination format is not blittable %d", dst.getFormat())) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
// src and dst should have the same aspect here
|
||||
VkImageAspectFlags const aspect = src.texture->getImageAspect();
|
||||
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst,
|
||||
args.srcRectPair, args.dstRectPair);
|
||||
}
|
||||
|
||||
void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
const VulkanAttachment src = args.srcTarget->getDepth();
|
||||
const VulkanAttachment dst = args.dstTarget->getDepth();
|
||||
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
assert_invariant(!(aspect & VK_IMAGE_ASPECT_DEPTH_BIT));
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
@@ -177,286 +186,40 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
}
|
||||
#endif
|
||||
|
||||
assert_invariant(src.texture && dst.texture);
|
||||
|
||||
if (src.texture->samples > 1 && dst.texture->samples == 1) {
|
||||
blitSlowDepth(args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
args.dstRectPair);
|
||||
return;
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
args.dstRectPair);
|
||||
resolveFast(cmdbuffer, aspect, src, dst);
|
||||
}
|
||||
|
||||
void VulkanBlitter::blit(VkFilter filter,
|
||||
VulkanAttachment dst, const VkOffset3D* dstRectPair,
|
||||
VulkanAttachment src, const VkOffset3D* srcRectPair) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
||||
"Depth src format is not blittable %d", src.getFormat())) {
|
||||
return;
|
||||
}
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
||||
"Depth dst format is not blittable %d", dst.getFormat())) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// src and dst should have the same aspect here
|
||||
VkImageAspectFlags const aspect = src.texture->getImageAspect();
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(cmdbuffer, aspect, filter, src, dst, srcRectPair, dstRectPair);
|
||||
}
|
||||
|
||||
void VulkanBlitter::terminate() noexcept {
|
||||
if (mDepthResolveProgram) {
|
||||
delete mDepthResolveProgram;
|
||||
mDepthResolveProgram = nullptr;
|
||||
}
|
||||
|
||||
if (mTriangleBuffer) {
|
||||
delete mTriangleBuffer;
|
||||
mTriangleBuffer = nullptr;
|
||||
}
|
||||
|
||||
if (mParamsBuffer) {
|
||||
delete mParamsBuffer;
|
||||
mParamsBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// If we created these shader modules in the constructor, the device might not be ready yet.
|
||||
// It is easier to do lazy initialization, which can also improve load time.
|
||||
void VulkanBlitter::lazyInit() noexcept {
|
||||
if (mDepthResolveProgram != nullptr) {
|
||||
return;
|
||||
}
|
||||
assert_invariant(mDevice);
|
||||
|
||||
auto decode = [device = mDevice](const uint8_t* compressed, int compressedSize) {
|
||||
const size_t spirvSize = smolv::GetDecodedBufferSize(compressed, compressedSize);
|
||||
FixedCapacityVector<uint8_t> spirv(spirvSize);
|
||||
smolv::Decode(compressed, compressedSize, spirv.data(), spirvSize);
|
||||
|
||||
VkShaderModuleCreateInfo moduleInfo = {};
|
||||
moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleInfo.codeSize = spirvSize;
|
||||
moduleInfo.pCode = (uint32_t*) spirv.data();
|
||||
|
||||
VkShaderModule result = VK_NULL_HANDLE;
|
||||
vkCreateShaderModule(device, &moduleInfo, VKALLOC, &result);
|
||||
assert_invariant(result);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
VkShaderModule vertexShader = decode(VKSHADERS_BLITDEPTHVS_DATA, VKSHADERS_BLITDEPTHVS_SIZE);
|
||||
VkShaderModule fragmentShader = decode(VKSHADERS_BLITDEPTHFS_DATA, VKSHADERS_BLITDEPTHFS_SIZE);
|
||||
|
||||
// Allocate one anonymous sampler at slot 0.
|
||||
VulkanProgram::CustomSamplerInfoList samplers = {
|
||||
{0, 0, ShaderStageFlags::FRAGMENT},
|
||||
};
|
||||
mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader, samplers);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLITTER)
|
||||
utils::slog.d << "Created Shader Module for VulkanBlitter "
|
||||
<< "shaders = (" << vertexShader << ", " << fragmentShader << ")"
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
static const float kTriangleVertices[] = {
|
||||
-1.0f, -1.0f,
|
||||
+1.0f, -1.0f,
|
||||
-1.0f, +1.0f,
|
||||
+1.0f, +1.0f,
|
||||
};
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
|
||||
mTriangleBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
sizeof(kTriangleVertices));
|
||||
|
||||
mTriangleBuffer->loadFromCpu(cmdbuffer, kTriangleVertices, 0, sizeof(kTriangleVertices));
|
||||
|
||||
mParamsBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
sizeof(BlitterUniforms));
|
||||
}
|
||||
|
||||
// At a high level, the procedure for resolving depth looks like this:
|
||||
// 1. Load uniforms
|
||||
// 2. Begin render pass
|
||||
// 3. Draw a big triangle
|
||||
// 4. End render pass.
|
||||
void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
|
||||
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
lazyInit();
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
commands->acquire(src.texture);
|
||||
commands->acquire(dst.texture);
|
||||
|
||||
BlitterUniforms const uniforms = {
|
||||
.sampleCount = src.texture->samples,
|
||||
.inverseSampleCount = 1.0f / float(src.texture->samples),
|
||||
};
|
||||
mParamsBuffer->loadFromCpu(cmdbuffer, &uniforms, 0, sizeof(uniforms));
|
||||
|
||||
VkImageAspectFlags const aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
// We will transition the src into sampler layout and also keep it in sampler layout for
|
||||
// consistency.
|
||||
VulkanLayout const samplerLayout = VulkanLayout::DEPTH_SAMPLER;
|
||||
|
||||
// BEGIN RENDER PASS
|
||||
// -----------------
|
||||
|
||||
const VulkanFboCache::RenderPassKey rpkey = {
|
||||
.initialColorLayoutMask = 0,
|
||||
.initialDepthLayout = VulkanLayout::UNDEFINED,
|
||||
.renderPassDepthLayout = samplerLayout,
|
||||
.finalDepthLayout = samplerLayout,
|
||||
.depthFormat = dst.getFormat(),
|
||||
.clear = {},
|
||||
.discardStart = TargetBufferFlags::DEPTH,
|
||||
.samples = 1,
|
||||
};
|
||||
|
||||
const VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
||||
mPipelineCache.bindRenderPass(renderPass, 0);
|
||||
|
||||
const VulkanFboCache::FboKey fbkey {
|
||||
.renderPass = renderPass,
|
||||
.width = (uint16_t) (dst.texture->width >> dst.level),
|
||||
.height = (uint16_t) (dst.texture->height >> dst.level),
|
||||
.layers = 1,
|
||||
.samples = rpkey.samples,
|
||||
.color = {},
|
||||
.resolve = {},
|
||||
.depth = dst.getImageView(aspect),
|
||||
};
|
||||
const VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
|
||||
VkRenderPassBeginInfo renderPassInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = renderPass,
|
||||
.framebuffer = vkfb,
|
||||
};
|
||||
renderPassInfo.renderArea.offset.x = dstRect[0].x;
|
||||
renderPassInfo.renderArea.offset.y = dstRect[0].y;
|
||||
renderPassInfo.renderArea.extent.width = dstRect[1].x - dstRect[0].x;
|
||||
renderPassInfo.renderArea.extent.height = dstRect[1].y - dstRect[0].y;
|
||||
|
||||
// We need to transition the source into a sampler since it'll be sampled in the shader.
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = src.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = src.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, samplerLayout);
|
||||
|
||||
vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = {
|
||||
.x = (float) dstRect[0].x,
|
||||
.y = (float) dstRect[0].y,
|
||||
.width = (float) renderPassInfo.renderArea.extent.width,
|
||||
.height = (float) renderPassInfo.renderArea.extent.height,
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f
|
||||
};
|
||||
|
||||
vkCmdSetViewport(cmdbuffer, 0, 1, &viewport);
|
||||
|
||||
// DRAW THE TRIANGLE
|
||||
// -----------------
|
||||
|
||||
mPipelineCache.bindProgram(mDepthResolveProgram);
|
||||
mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
|
||||
|
||||
auto vkraster = mPipelineCache.getCurrentRasterState();
|
||||
vkraster.depthWriteEnable = true;
|
||||
vkraster.depthCompareOp = SamplerCompareFunc::A;
|
||||
vkraster.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||
vkraster.alphaToCoverageEnable = false,
|
||||
vkraster.blendEnable = VK_FALSE,
|
||||
vkraster.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
vkraster.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
vkraster.colorBlendOp = BlendEquation::ADD,
|
||||
vkraster.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
vkraster.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
vkraster.alphaBlendOp = BlendEquation::ADD,
|
||||
vkraster.colorWriteMask = (VkColorComponentFlags) 0,
|
||||
vkraster.cullMode = VK_CULL_MODE_NONE;
|
||||
vkraster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
vkraster.depthBiasEnable = VK_FALSE;
|
||||
vkraster.colorTargetCount = 0;
|
||||
mPipelineCache.bindRasterState(vkraster);
|
||||
|
||||
VkBuffer buffers[1] = {};
|
||||
VkDeviceSize offsets[1] = {};
|
||||
buffers[0] = mTriangleBuffer->getGpuBuffer();
|
||||
VkVertexInputAttributeDescription attribDesc = {
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32_SFLOAT,
|
||||
};
|
||||
VkVertexInputBindingDescription bufferDesc = {
|
||||
.binding = 0,
|
||||
.stride = sizeof(float) * 2,
|
||||
};
|
||||
mPipelineCache.bindVertexArray(&attribDesc, &bufferDesc, 1);
|
||||
|
||||
// Select nearest filtering and clamp_to_edge.
|
||||
VkSampler vksampler = mSamplerCache.getSampler({});
|
||||
|
||||
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT];
|
||||
VulkanTexture* textures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||
for (auto& sampler : samplers) {
|
||||
sampler = {
|
||||
.sampler = vksampler,
|
||||
.imageView = mEmptyTexture->getPrimaryImageView(),
|
||||
.imageLayout = ImgUtil::getVkLayout(VulkanLayout::READ_WRITE),
|
||||
};
|
||||
}
|
||||
|
||||
samplers[0] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
|
||||
.imageLayout = ImgUtil::getVkLayout(samplerLayout),
|
||||
};
|
||||
textures[0] = src.texture;
|
||||
|
||||
mPipelineCache.bindSamplers(samplers, textures,
|
||||
VulkanPipelineCache::getUsageFlags(0, ShaderStageFlags::FRAGMENT));
|
||||
|
||||
auto previousUbo = mPipelineCache.getUniformBufferBinding(0);
|
||||
mPipelineCache.bindUniformBuffer(0, mParamsBuffer->getGpuBuffer());
|
||||
|
||||
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
|
||||
assert_invariant(false);
|
||||
}
|
||||
|
||||
const VkRect2D scissor = {
|
||||
.offset = renderPassInfo.renderArea.offset,
|
||||
.extent = renderPassInfo.renderArea.extent,
|
||||
};
|
||||
|
||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||
|
||||
if (!mPipelineCache.bindPipeline(commands)) {
|
||||
assert_invariant(false);
|
||||
}
|
||||
|
||||
vkCmdBindVertexBuffers(cmdbuffer, 0, 1, buffers, offsets);
|
||||
vkCmdDraw(cmdbuffer, 4, 1, 0, 0);
|
||||
|
||||
// END RENDER PASS
|
||||
// ---------------
|
||||
|
||||
vkCmdEndRenderPass(cmdbuffer);
|
||||
|
||||
VkMemoryBarrier barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
};
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
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);
|
||||
|
||||
mPipelineCache.bindUniformBuffer(0, previousUbo.buffer, previousUbo.offset, previousUbo.size);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -32,46 +32,24 @@ struct VulkanProgram;
|
||||
|
||||
class VulkanBlitter {
|
||||
public:
|
||||
VulkanBlitter(VulkanStagePool& stagePool, VulkanPipelineCache& pipelineCache,
|
||||
VulkanFboCache& fboCache, VulkanSamplerCache& samplerCache) noexcept;
|
||||
VulkanBlitter() noexcept;
|
||||
|
||||
void initialize(VkPhysicalDevice physicalDevice, VkDevice device, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept;
|
||||
void initialize(VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
VmaAllocator allocator, VulkanCommands* commands) noexcept;
|
||||
|
||||
struct BlitArgs {
|
||||
const VulkanRenderTarget* dstTarget;
|
||||
const VkOffset3D* dstRectPair;
|
||||
const VulkanRenderTarget* srcTarget;
|
||||
const VkOffset3D* srcRectPair;
|
||||
VkFilter filter = VK_FILTER_NEAREST;
|
||||
int targetIndex = 0;
|
||||
};
|
||||
void blit(VkFilter filter,
|
||||
VulkanAttachment dst, const VkOffset3D* dstRectPair,
|
||||
VulkanAttachment src, const VkOffset3D* srcRectPair);
|
||||
|
||||
void blitColor(BlitArgs args);
|
||||
void blitDepth(BlitArgs args);
|
||||
void resolve(VulkanAttachment dst, VulkanAttachment src);
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
void lazyInit() noexcept;
|
||||
|
||||
void blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
|
||||
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]);
|
||||
|
||||
VulkanBuffer* mTriangleBuffer = nullptr;
|
||||
VulkanBuffer* mParamsBuffer = nullptr;
|
||||
VulkanProgram* mDepthResolveProgram = nullptr;
|
||||
|
||||
UTILS_UNUSED VkPhysicalDevice mPhysicalDevice;
|
||||
VkDevice mDevice;
|
||||
VmaAllocator mAllocator;
|
||||
VulkanCommands* mCommands;
|
||||
VulkanTexture* mEmptyTexture;
|
||||
|
||||
VulkanStagePool& mStagePool;
|
||||
VulkanPipelineCache& mPipelineCache;
|
||||
VulkanFboCache& mFramebufferCache;
|
||||
VulkanSamplerCache& mSamplerCache;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -54,7 +54,8 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
|
||||
VmaAllocator allocator;
|
||||
VmaVulkanFunctions const funcs {
|
||||
#if VMA_DYNAMIC_VULKAN_FUNCTIONS
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr, .vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
#else
|
||||
.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties,
|
||||
.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties,
|
||||
@@ -155,7 +156,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mResourceManager(&mResourceAllocator),
|
||||
mThreadSafeResourceManager(&mResourceAllocator),
|
||||
mPipelineCache(&mResourceAllocator),
|
||||
mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
|
||||
|
||||
@@ -205,7 +205,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
|
||||
mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView());
|
||||
mBlitter.initialize(mPlatform->getPhysicalDevice(), mPlatform->getDevice(), mAllocator,
|
||||
mCommands.get(), mEmptyTexture.get());
|
||||
mCommands.get());
|
||||
}
|
||||
|
||||
VulkanDriver::~VulkanDriver() noexcept = default;
|
||||
@@ -426,7 +426,8 @@ void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
}
|
||||
|
||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
auto vkprogram = mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||
auto vkprogram
|
||||
= mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||
mResourceManager.acquire(vkprogram);
|
||||
}
|
||||
|
||||
@@ -755,6 +756,7 @@ bool VulkanDriver::isFrameTimeSupported() {
|
||||
}
|
||||
|
||||
bool VulkanDriver::isAutoDepthResolveSupported() {
|
||||
// TODO: this could be supported with vk 1.2 or VK_KHR_depth_stencil_resolve
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -770,6 +772,11 @@ bool VulkanDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isDepthStencilResolveSupported() {
|
||||
// TODO: apparently it could be supported in core 1.2 and/or with VK_KHR_depth_stencil_resolve
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU: {
|
||||
@@ -953,10 +960,43 @@ void VulkanDriver::setExternalImagePlane(Handle<HwTexture> th, void* image, uint
|
||||
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
||||
}
|
||||
|
||||
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) { }
|
||||
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
auto* const t = mResourceAllocator.handle_cast<VulkanTexture*>(th);
|
||||
assert_invariant(t);
|
||||
|
||||
bool VulkanDriver::canGenerateMipmaps() {
|
||||
return false;
|
||||
int32_t layerCount = int32_t(t->depth);
|
||||
if (t->target == SamplerType::SAMPLER_CUBEMAP_ARRAY ||
|
||||
t->target == SamplerType::SAMPLER_CUBEMAP) {
|
||||
layerCount *= 6;
|
||||
}
|
||||
|
||||
// FIXME: the loop below can perform many layout transitions and back. We should be
|
||||
// able to optimize that.
|
||||
|
||||
uint8_t level = 0;
|
||||
int32_t srcw = int32_t(t->width);
|
||||
int32_t srch = int32_t(t->height);
|
||||
do {
|
||||
int32_t const dstw = std::max(srcw >> 1, 1);
|
||||
int32_t const dsth = std::max(srch >> 1, 1);
|
||||
const VkOffset3D srcOffsets[2] = {{ 0, 0, 0 }, { srcw, srch, 1 }};
|
||||
const VkOffset3D dstOffsets[2] = {{ 0, 0, 0 }, { dstw, dsth, 1 }};
|
||||
|
||||
// TODO: there should be a way to do this using layerCount in vkBlitImage
|
||||
// TODO: vkBlitImage should be able to handle 3D textures too
|
||||
for (int32_t layer = 0; layer < layerCount; layer++) {
|
||||
mBlitter.blit(VK_FILTER_LINEAR,
|
||||
{ .texture = t, .level = uint8_t(level + 1), .layer = (uint16_t)layer },
|
||||
dstOffsets,
|
||||
{ .texture = t, .level = uint8_t(level ), .layer = (uint16_t)layer },
|
||||
srcOffsets);
|
||||
}
|
||||
|
||||
level++;
|
||||
srcw = dstw;
|
||||
srch = dsth;
|
||||
} while ((srcw > 1 || srch > 1) && level < t->levels);
|
||||
t->setPrimaryRange(0, t->levels - 1);
|
||||
}
|
||||
|
||||
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
@@ -1437,50 +1477,138 @@ void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect, SamplerMagFilter filter) {
|
||||
void VulkanDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("resolve");
|
||||
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"resolve() cannot be invoked inside a render pass.");
|
||||
|
||||
auto* const srcTexture = mResourceAllocator.handle_cast<VulkanTexture*>(src);
|
||||
auto* const dstTexture = mResourceAllocator.handle_cast<VulkanTexture*>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+srcTexture->samples, +dstTexture->samples);
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format),
|
||||
"can't resolve depth formats");
|
||||
|
||||
ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format),
|
||||
"can't resolve stencil formats");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
mBlitter.resolve(
|
||||
{ .texture = dstTexture, .level = dstLevel, .layer = dstLayer },
|
||||
{ .texture = srcTexture, .level = srcLevel, .layer = srcLayer });
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("blit");
|
||||
|
||||
assert_invariant(mCurrentRenderPass.renderPass == VK_NULL_HANDLE);
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"blit() cannot be invoked inside a render pass.");
|
||||
|
||||
// blit operation only support COLOR0 color buffer
|
||||
assert_invariant(
|
||||
!(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
auto* const srcTexture = mResourceAllocator.handle_cast<VulkanTexture*>(src);
|
||||
auto* const dstTexture = mResourceAllocator.handle_cast<VulkanTexture*>(dst);
|
||||
|
||||
if (UTILS_UNLIKELY(mCurrentRenderPass.renderPass)) {
|
||||
utils::slog.e << "Blits cannot be invoked inside a render pass." << utils::io::endl;
|
||||
return;
|
||||
}
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
// The Y inversion below makes it so that Vk matches GL and Metal.
|
||||
|
||||
auto const srcLeft = int32_t(srcOrigin.x);
|
||||
auto const dstLeft = int32_t(dstOrigin.x);
|
||||
auto const srcTop = int32_t(srcTexture->height - (srcOrigin.y + size.y));
|
||||
auto const dstTop = int32_t(dstTexture->height - (dstOrigin.y + size.y));
|
||||
auto const srcRight = int32_t(srcOrigin.x + size.x);
|
||||
auto const dstRight = int32_t(dstOrigin.x + size.x);
|
||||
auto const srcBottom = int32_t(srcTop + size.y);
|
||||
auto const dstBottom = int32_t(dstTop + size.y);
|
||||
VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
|
||||
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
|
||||
|
||||
// no scaling guaranteed
|
||||
mBlitter.blit(VK_FILTER_NEAREST,
|
||||
{ .texture = dstTexture, .level = dstLevel, .layer = dstLayer }, dstOffsets,
|
||||
{ .texture = srcTexture, .level = srcLevel, .layer = srcLayer }, srcOffsets);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("blitDEPRECATED");
|
||||
|
||||
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
||||
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"blitDEPRECATED() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
|
||||
VulkanRenderTarget* dstTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(dst);
|
||||
VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(src);
|
||||
|
||||
VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
VkFilter const vkfilter = (filter == SamplerMagFilter::NEAREST) ?
|
||||
VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
|
||||
// The Y inversion below makes it so that Vk matches GL and Metal.
|
||||
|
||||
const VkExtent2D srcExtent = srcTarget->getExtent();
|
||||
const int32_t srcLeft = srcRect.left;
|
||||
const int32_t srcTop = srcExtent.height - srcRect.bottom - srcRect.height;
|
||||
const int32_t srcRight = srcRect.left + srcRect.width;
|
||||
const int32_t srcBottom = srcTop + srcRect.height;
|
||||
const VkOffset3D srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
|
||||
VkExtent2D const srcExtent = srcTarget->getExtent();
|
||||
VkExtent2D const dstExtent = dstTarget->getExtent();
|
||||
|
||||
const VkExtent2D dstExtent = dstTarget->getExtent();
|
||||
const int32_t dstLeft = dstRect.left;
|
||||
const int32_t dstTop = dstExtent.height - dstRect.bottom - dstRect.height;
|
||||
const int32_t dstRight = dstRect.left + dstRect.width;
|
||||
const int32_t dstBottom = dstTop + dstRect.height;
|
||||
const VkOffset3D dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
|
||||
auto const dstLeft = int32_t(dstRect.left);
|
||||
auto const srcLeft = int32_t(srcRect.left);
|
||||
auto const dstTop = int32_t(dstExtent.height - (dstRect.bottom + dstRect.height));
|
||||
auto const srcTop = int32_t(srcExtent.height - (srcRect.bottom + srcRect.height));
|
||||
auto const dstRight = int32_t(dstRect.left + dstRect.width);
|
||||
auto const srcRight = int32_t(srcRect.left + srcRect.width);
|
||||
auto const dstBottom = int32_t(dstTop + dstRect.height);
|
||||
auto const srcBottom = int32_t(srcTop + srcRect.height);
|
||||
VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
|
||||
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
|
||||
|
||||
if (any(buffers & TargetBufferFlags::DEPTH) && srcTarget->hasDepth() && dstTarget->hasDepth()) {
|
||||
mBlitter.blitDepth({dstTarget, dstOffsets, srcTarget, srcOffsets});
|
||||
}
|
||||
mBlitter.blit(vkfilter,
|
||||
dstTarget->getColor(0), dstOffsets,
|
||||
srcTarget->getColor(0), srcOffsets);
|
||||
|
||||
if (any(buffers & TargetBufferFlags::COLOR0)) {
|
||||
mBlitter.blitColor({ dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, int(0) });
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "spirv/VulkanSpirvUtils.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Panic.h> // ASSERT_POSTCONDITION
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -46,61 +47,39 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
|
||||
rect->extent.height = std::max(top - y, 0);
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
|
||||
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
: HwProgram(builder.getName()),
|
||||
VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo(builder.getSpecializationConstants().size())),
|
||||
mInfo(new PipelineInfo()),
|
||||
mDevice(device) {
|
||||
auto& blobs = builder.getShadersSource();
|
||||
Program::ShaderSource const& blobs = builder.getShadersSource();
|
||||
auto& modules = mInfo->shaders;
|
||||
|
||||
auto const& specializationConstants = builder.getSpecializationConstants();
|
||||
|
||||
std::vector<uint32_t> shader;
|
||||
for (size_t i = 0; i < MAX_SHADER_MODULES; i++) {
|
||||
const auto& blob = blobs[i];
|
||||
uint32_t* data = (uint32_t*)blob.data();
|
||||
Program::ShaderBlob const& blob = blobs[i];
|
||||
|
||||
uint32_t* data = (uint32_t*) blob.data();
|
||||
size_t dataSize = blob.size();
|
||||
|
||||
if (!specializationConstants.empty()) {
|
||||
workaroundSpecConstant(blob, specializationConstants, shader);
|
||||
data = (uint32_t*) shader.data();
|
||||
dataSize = shader.size() * 4;
|
||||
}
|
||||
|
||||
VkShaderModule& module = modules[i];
|
||||
VkShaderModuleCreateInfo moduleInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
.codeSize = blob.size(),
|
||||
.codeSize = dataSize,
|
||||
.pCode = data,
|
||||
};
|
||||
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module.");
|
||||
}
|
||||
|
||||
// Note that bools are 4-bytes in Vulkan
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
|
||||
constexpr uint32_t const CONSTANT_SIZE = 4;
|
||||
|
||||
// populate the specialization constants requirements right now
|
||||
auto const& specializationConstants = builder.getSpecializationConstants();
|
||||
uint32_t const specConstCount = static_cast<uint32_t>(specializationConstants.size());
|
||||
char* specData = mInfo->specConstData.get();
|
||||
if (specConstCount > 0) {
|
||||
mInfo->specializationInfo = {
|
||||
.mapEntryCount = specConstCount,
|
||||
.pMapEntries = mInfo->specConsts.data(),
|
||||
.dataSize = specConstCount * CONSTANT_SIZE,
|
||||
.pData = specData,
|
||||
};
|
||||
}
|
||||
for (uint32_t i = 0; i < specConstCount; ++i) {
|
||||
uint32_t const offset = i * CONSTANT_SIZE;
|
||||
mInfo->specConsts[i] = {
|
||||
.constantID = specializationConstants[i].id,
|
||||
.offset = offset,
|
||||
.size = CONSTANT_SIZE,
|
||||
};
|
||||
using SpecConstant = Program::SpecializationConstant::Type;
|
||||
char const* addr = (char*)specData + offset;
|
||||
SpecConstant const& arg = specializationConstants[i].value;
|
||||
if (std::holds_alternative<bool>(arg)) {
|
||||
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
|
||||
} else if (std::holds_alternative<float>(arg)) {
|
||||
*((float*)addr) = std::get<float>(arg);
|
||||
} else {
|
||||
*((int32_t*)addr) = std::get<int32_t>(arg);
|
||||
}
|
||||
}
|
||||
|
||||
auto& groupInfo = builder.getSamplerGroupInfo();
|
||||
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
|
||||
auto& usage = mInfo->usage;
|
||||
@@ -123,7 +102,7 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
|
||||
CustomSamplerInfoList const& samplerInfo) noexcept
|
||||
: VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo(0)),
|
||||
mInfo(new PipelineInfo()),
|
||||
mDevice(device) {
|
||||
mInfo->shaders[0] = vs;
|
||||
mInfo->shaders[1] = fs;
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanSwapChain.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
@@ -38,7 +37,7 @@ class VulkanTimestamps;
|
||||
|
||||
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
VulkanProgram(VkDevice device, const Program& builder) noexcept;
|
||||
VulkanProgram(VkDevice device, Program const& builder) noexcept;
|
||||
|
||||
struct CustomSamplerInfo {
|
||||
uint8_t groupIndex;
|
||||
@@ -65,21 +64,14 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
return mInfo->bindingToSamplerIndex;
|
||||
}
|
||||
|
||||
inline VkSpecializationInfo const& getSpecConstInfo() const {
|
||||
return mInfo->specializationInfo;
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: handle compute shaders.
|
||||
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
|
||||
static constexpr uint8_t MAX_SHADER_MODULES = 2;
|
||||
static constexpr uint8_t const MAX_SHADER_MODULES = 2;
|
||||
|
||||
struct PipelineInfo {
|
||||
PipelineInfo(size_t specConstsCount) :
|
||||
bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
|
||||
specConsts(specConstsCount, VkSpecializationMapEntry{}),
|
||||
specConstData(new char[specConstsCount * 4])
|
||||
{}
|
||||
PipelineInfo()
|
||||
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) {}
|
||||
|
||||
// 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).
|
||||
@@ -87,10 +79,7 @@ private:
|
||||
|
||||
// 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};
|
||||
VkSpecializationInfo specializationInfo = {};
|
||||
utils::FixedCapacityVector<VkSpecializationMapEntry> specConsts;
|
||||
std::unique_ptr<char[]> specConstData;
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
|
||||
@@ -360,9 +360,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
|
||||
// If we reach this point, we need to create and stash a brand new pipeline object.
|
||||
shaderStages[0].module = mPipelineRequirements.shaders[0];
|
||||
shaderStages[0].pSpecializationInfo = mSpecializationRequirements;
|
||||
shaderStages[1].module = mPipelineRequirements.shaders[1];
|
||||
shaderStages[1].pSpecializationInfo = mSpecializationRequirements;
|
||||
|
||||
// Expand our size-optimized structs into the proper Vk structs.
|
||||
uint32_t numVertexAttribs = 0;
|
||||
@@ -568,7 +566,6 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP
|
||||
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
|
||||
mPipelineRequirements.shaders[0] = program->getVertexShader();
|
||||
mPipelineRequirements.shaders[1] = program->getFragmentShader();
|
||||
mSpecializationRequirements = &program->getSpecConstInfo();
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept {
|
||||
|
||||
@@ -416,7 +416,6 @@ private:
|
||||
RasterState mCurrentRasterState;
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
DescriptorKey mDescriptorRequirements = {};
|
||||
VkSpecializationInfo const* mSpecializationRequirements = nullptr;
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanDriverFactory.h"
|
||||
@@ -23,6 +24,11 @@
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#define LINUX_OR_FREEBSD 1
|
||||
#endif
|
||||
@@ -73,7 +79,7 @@
|
||||
#elif defined(WIN32)
|
||||
// No platform specific includes
|
||||
#else
|
||||
#error Not a supported Vulkan platform
|
||||
// Not a supported Vulkan platform
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanSpirvUtils.h"
|
||||
|
||||
#include <utils/compiler.h> // UTILS_UNUSED_IN_RELEASE, UTILS_FALLTHROUGH
|
||||
#include <utils/debug.h> // assert_invariant
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// This function transforms an OpSpecConstant instruction into just a OpConstant instruction.
|
||||
// Additionally, it will adjust the value of the constant as given by the program.
|
||||
void getTransformedConstantInst(SpecConstantValue* value, uint32_t* inst) {
|
||||
|
||||
// The first word is the size of the instruction and the instruction type.
|
||||
// The second word is the type of the instruction.
|
||||
// The third word is the "result id" (i.e. the variable to store into).
|
||||
// The fourth word (only for floats and ints) is the literal representing the value.
|
||||
// We only want to change the first and fourth words.
|
||||
constexpr size_t const OP = 0;
|
||||
constexpr size_t const VALUE = 3;
|
||||
|
||||
if (!value) {
|
||||
// If a specialization wasn't provided, just switch the opcode.
|
||||
uint32_t const op = inst[OP] & 0x0000FFFF;
|
||||
if (op == spv::Op::OpSpecConstantFalse) {
|
||||
inst[OP] = (3 << 16) | spv::Op::OpConstantFalse;
|
||||
} else if (op == spv::Op::OpSpecConstantTrue) {
|
||||
inst[OP] = (3 << 16) | spv::Op::OpConstantTrue;
|
||||
} else if (op == spv::Op::OpSpecConstant) {
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
}
|
||||
} else if (std::holds_alternative<bool>(*value)) {
|
||||
inst[OP] = (3 << 16)
|
||||
| (std::get<bool>(*value) ? spv::Op::OpConstantTrue : spv::Op::OpConstantFalse);
|
||||
} else if (std::holds_alternative<float>(*value)) {
|
||||
float const fval = std::get<float>(*value);
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
inst[VALUE] = *reinterpret_cast<uint32_t const*>(&fval);
|
||||
} else {
|
||||
int const ival = std::get<int>(*value);
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
inst[VALUE] = *reinterpret_cast<uint32_t const*>(&ival);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void workaroundSpecConstant(Program::ShaderBlob const& blob,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants,
|
||||
std::vector<uint32_t>& output) {
|
||||
using WordMap = std::unordered_map<uint32_t, uint32_t>;
|
||||
using SpecValueMap = std::unordered_map<uint32_t, SpecConstantValue>;
|
||||
constexpr size_t const HEADER_SIZE = 5;
|
||||
|
||||
WordMap varToIdMap;
|
||||
SpecValueMap idToValue;
|
||||
|
||||
for (auto spec: specConstants) {
|
||||
idToValue[spec.id] = spec.value;
|
||||
}
|
||||
|
||||
// The follow loop will iterate through all the instructions and look for instructions of the
|
||||
// form:
|
||||
//
|
||||
// OpDecorate %1 SpecId 0
|
||||
// %1 = OpSpecConstantFalse %bool
|
||||
//
|
||||
// We want to track the mapping between the variable id %1 and the specId 0. And when seeing a
|
||||
// %1 as the result, we want to change that instruction to just an OpConsant where the constant
|
||||
// value is adjusted by the spec values provided in the program.
|
||||
// Additionally, we will turn the SpecId decorations to no-ops.
|
||||
size_t const dataSize = blob.size() / 4;
|
||||
|
||||
output.resize(dataSize);
|
||||
uint32_t const* data = (uint32_t*) blob.data();
|
||||
uint32_t* outputData = output.data();
|
||||
|
||||
std::memcpy(&outputData[0], &data[0], HEADER_SIZE * 4);
|
||||
size_t outputCursor = HEADER_SIZE;
|
||||
|
||||
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
|
||||
uint32_t const firstWord = data[cursor];
|
||||
uint32_t const wordCount = firstWord >> 16;
|
||||
uint32_t const op = firstWord & 0x0000FFFF;
|
||||
|
||||
switch(op) {
|
||||
case spv::Op::OpSpecConstant:
|
||||
case spv::Op::OpSpecConstantTrue:
|
||||
case spv::Op::OpSpecConstantFalse: {
|
||||
uint32_t const targetVar = data[cursor + 2];
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE WordMap::const_iterator idItr = varToIdMap.find(targetVar);
|
||||
assert_invariant(idItr != varToIdMap.end() &&
|
||||
"Cannot find variable previously decorated with SpecId");
|
||||
|
||||
auto valItr = idToValue.find(idItr->second);
|
||||
|
||||
SpecConstantValue* val = (valItr != idToValue.end()) ? &valItr->second : nullptr;
|
||||
std::memcpy(&outputData[outputCursor], &data[cursor], wordCount * 4);
|
||||
getTransformedConstantInst(val, &outputData[outputCursor]);
|
||||
outputCursor += wordCount;
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpDecorate: {
|
||||
if (data[cursor + 2] == spv::Decoration::DecorationSpecId) {
|
||||
uint32_t const targetVar = data[cursor + 1];
|
||||
uint32_t const specId = data[cursor + 3];
|
||||
varToIdMap[targetVar] = specId;
|
||||
|
||||
// Note these decorations do not need to be written to the output.
|
||||
break;
|
||||
}
|
||||
// else fallthrough and copy like all other instructions
|
||||
UTILS_FALLTHROUGH;
|
||||
}
|
||||
default:
|
||||
std::memcpy(&outputData[outputCursor], &data[cursor], wordCount * 4);
|
||||
outputCursor += wordCount;
|
||||
break;
|
||||
}
|
||||
cursor += wordCount;
|
||||
}
|
||||
output.resize(outputCursor);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
|
||||
|
||||
#include "backend/Program.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using SpecConstantValue = Program::SpecializationConstant::Type;
|
||||
|
||||
// For certain drivers, using spec constant can lead to compile errors [1] or undesirable behaviors
|
||||
// [2]. In those instances, we simply change the spirv and set them to constants.
|
||||
//
|
||||
// (Implemenation note: we cannot write to the blob because spirv-validator does not properly handle
|
||||
// the Nop (no-op) instruction, and swiftshader validates the shader before compilation. So we need
|
||||
// to skip those instructions instead).
|
||||
//
|
||||
// [1]: QC driver cannot use spec constant to size fields
|
||||
// (https://github.com/google/filament/issues/6444).
|
||||
// [2]: An internal driver does not DCE a block guarded by a spec-const boolean set to false
|
||||
// (b/310603393).
|
||||
void workaroundSpecConstant(Program::ShaderBlob const& blob,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants,
|
||||
std::vector<uint32_t>& output);
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user