Compare commits
116
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab552d3619 | ||
|
|
4fd7c418e5 | ||
|
|
a3fdca7997 | ||
|
|
7f1704481e | ||
|
|
e6b962b038 | ||
|
|
668fb07ac2 | ||
|
|
f64eef02a3 | ||
|
|
239e98ccec | ||
|
|
919cfae6b2 | ||
|
|
0b9389430b | ||
|
|
1d157677d1 | ||
|
|
ef3f0cb326 | ||
|
|
fcf53f2c3e | ||
|
|
d6fda03b06 | ||
|
|
a76addd2bf | ||
|
|
b1f7731dbe | ||
|
|
e3e12dbf73 | ||
|
|
31a75029f0 | ||
|
|
e5c24cc718 | ||
|
|
2b86c8df6f | ||
|
|
4d8e6eefa1 | ||
|
|
8aeec2ba35 | ||
|
|
4eb4fd5aba | ||
|
|
56355231bd | ||
|
|
9ccb8fce31 | ||
|
|
e674420e9c | ||
|
|
8a9cbcfb99 | ||
|
|
f0d5cd3fa1 | ||
|
|
73b0751ccf | ||
|
|
cc95a4a7a3 | ||
|
|
d76cf643c5 | ||
|
|
6e249c4c1b | ||
|
|
af0c6a7fe9 | ||
|
|
7b7dfad552 | ||
|
|
deb3eb0b11 | ||
|
|
d3016adaff | ||
|
|
6c29542fad | ||
|
|
0d2a96d630 | ||
|
|
d3fe46765f | ||
|
|
892f94e3c4 | ||
|
|
f75f7039f4 | ||
|
|
8303d6b28e | ||
|
|
0f9a2dd6af | ||
|
|
626621fb1c | ||
|
|
2b78fd8359 | ||
|
|
9d181a172a | ||
|
|
d4b9d1e023 | ||
|
|
b62991d967 | ||
|
|
562ea65d5c | ||
|
|
6498cf5b64 | ||
|
|
3c77d2c3f5 | ||
|
|
960c6170fe | ||
|
|
37c2fe31d5 | ||
|
|
21b51caf3d | ||
|
|
76dbc08176 | ||
|
|
1b0db0fca2 | ||
|
|
163f02035f | ||
|
|
14263efbea | ||
|
|
7c6103a458 | ||
|
|
92846305f5 | ||
|
|
d5ebca0c49 | ||
|
|
38ceee8d75 | ||
|
|
fc6744ba75 | ||
|
|
078a17469a | ||
|
|
0887e388db | ||
|
|
e4a57cedf9 | ||
|
|
d92bdce852 | ||
|
|
76bf906856 | ||
|
|
b5e23162df | ||
|
|
eeb53606c8 | ||
|
|
dd23f271e3 | ||
|
|
e78a06797c | ||
|
|
6279613b79 | ||
|
|
78d433cafa | ||
|
|
6590f62052 | ||
|
|
f1f7aeb14f | ||
|
|
5190b03f89 | ||
|
|
2cd492ed38 | ||
|
|
07975868fe | ||
|
|
5ab526cbb0 | ||
|
|
df935b75e5 | ||
|
|
b4d6f975c1 | ||
|
|
2cf86454bb | ||
|
|
5572097fb3 | ||
|
|
480cfb2820 | ||
|
|
39d555d115 | ||
|
|
df50f4c25d | ||
|
|
976f304ee7 | ||
|
|
21ea99a1d9 | ||
|
|
f0b0505207 | ||
|
|
11997d007a | ||
|
|
c0327c95d4 | ||
|
|
e1dfea0f12 | ||
|
|
b2cef9bfee | ||
|
|
f6b979c8e4 | ||
|
|
2e9dd03222 | ||
|
|
9a6b8bf24e | ||
|
|
3aee9402d5 | ||
|
|
fcf5866fc3 | ||
|
|
864a854c13 | ||
|
|
5a18cdf0f6 | ||
|
|
6ebd22f474 | ||
|
|
9f5c1bb0dc | ||
|
|
9cbf741ac2 | ||
|
|
04c8845284 | ||
|
|
d82b7a529c | ||
|
|
72c60b35d7 | ||
|
|
5d30435620 | ||
|
|
d02231dce9 | ||
|
|
ea70fe540f | ||
|
|
cb9bfaeb83 | ||
|
|
3dcdb202d1 | ||
|
|
da73f89a23 | ||
|
|
9474798c75 | ||
|
|
13df6652ae | ||
|
|
4f925f07e8 |
@@ -0,0 +1,7 @@
|
||||
;;; Directory Local Variables -*- no-byte-compile: t -*-
|
||||
;;; For more information see (info "(emacs) Directory Variables")
|
||||
|
||||
((c++-mode . ((c-file-style . "filament")
|
||||
(apheleia-inhibit . t)))
|
||||
(c-mode . ((c-file-style . "filament")
|
||||
(apheleia-inhibit . t))))
|
||||
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
|
||||
[*.{c,cpp,h,inc,kt,java,js,md}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 100
|
||||
@@ -29,10 +29,6 @@ jobs:
|
||||
with:
|
||||
name: filamat-android-full
|
||||
path: out/filamat-android-release.aar
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: filamat-android-lite
|
||||
path: out/filamat-android-lite-release.aar
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: gltfio-android-release
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: CocoaPods Deploy
|
||||
|
||||
# This must be run after the iOS release job has finished, and the iOS release
|
||||
# asset has been uploaded to Github.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Release tag to deploy (e.g., v1.42.2)'
|
||||
required: true
|
||||
default: 'v1.42.2'
|
||||
|
||||
jobs:
|
||||
cocoapods-deploy:
|
||||
name: cocoapods-deploy
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out iOS/CocoaPods directory
|
||||
uses: Bhacaz/checkout-files@49fc3050859046bf4f4873678d46099985640e89
|
||||
with:
|
||||
files: ios/CocoaPods
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.event.inputs.release_tag }}
|
||||
- name: Move podspec to root
|
||||
run: mv ios/CocoaPods/*.podspec .
|
||||
- name: Install CocoaPods
|
||||
run: gem install cocoapods
|
||||
- uses: michaelhenry/deploy-to-cocoapods-github-action@745686ab065f90596e0d5cfcf97bb2416d94262e
|
||||
env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Npm Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Release tag to deploy (e.g., v1.42.2)'
|
||||
required: true
|
||||
default: 'v1.42.2'
|
||||
|
||||
jobs:
|
||||
npm-deploy:
|
||||
name: npm-deploy
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
ref: ${{ github.event.inputs.release_tag }}
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/web && printf "y" | ./build.sh release
|
||||
- name: Deploy to npm
|
||||
run: |
|
||||
cd out/cmake-webgl-release/web/filament-js
|
||||
npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -124,7 +124,6 @@ jobs:
|
||||
cd ../..
|
||||
mv out/filament-android-release.aar out/filament-${TAG}-android.aar
|
||||
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
|
||||
mv out/filamat-android-lite-release.aar out/filamat-${TAG}-lite-android.aar
|
||||
mv out/gltfio-android-release.aar out/gltfio-${TAG}-android.aar
|
||||
mv out/filament-utils-android-release.aar out/filament-utils-${TAG}-android.aar
|
||||
- name: Sign sample-gltf-viewer
|
||||
|
||||
@@ -16,3 +16,5 @@ settings.json
|
||||
test*.png
|
||||
test*.json
|
||||
results
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
|
||||
+7
-5
@@ -56,9 +56,11 @@ To trigger both incremental debug and release builds:
|
||||
./build.sh debug release
|
||||
```
|
||||
|
||||
If build fails for some reasons, it may leave the `out/` directory in a broken state. You can
|
||||
force a clean build by adding the `-c` flag in that case.
|
||||
|
||||
To install the libraries and executables in `out/debug/` and `out/release/`, add the `-i` flag.
|
||||
You can force a clean build by adding the `-c` flag. The script offers more features described
|
||||
by executing `build.sh -h`.
|
||||
The script offers more features described by executing `build.sh -h`.
|
||||
|
||||
### Filament-specific CMake Options
|
||||
|
||||
@@ -172,12 +174,12 @@ See [ios/samples/README.md](./ios/samples/README.md) for more information.
|
||||
|
||||
### Windows
|
||||
|
||||
#### Building on Windows with Visual Studio 2019
|
||||
#### Building on Windows with Visual Studio 2019 or later
|
||||
|
||||
Install the following components:
|
||||
|
||||
- [Visual Studio 2019](https://www.visualstudio.com/downloads)
|
||||
- [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk)
|
||||
- [Visual Studio 2019 or later](https://www.visualstudio.com/downloads)
|
||||
- [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/)
|
||||
- [Python 3.7](https://www.python.org/ftp/python/3.7.0/python-3.7.0.exe)
|
||||
- [CMake 3.14 or later](https://github.com/Kitware/CMake/releases/download/v3.14.7/cmake-3.14.7-win64-x64.msi)
|
||||
|
||||
|
||||
@@ -7,3 +7,12 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: Allow instantiating Engine at a given feature level via `Engine::Builder::featureLevel`
|
||||
- matc: Enable `GL_OES_standard_derivatives` extension in ESSL 1.0 shaders
|
||||
- matc: Fix code generation of double sided and masked materials in ESSL 1.0 shaders
|
||||
- filagui: Add support for feature level 0
|
||||
- matc: Add support for post-process materials in feature level 0
|
||||
- engine: Add `Material::getFeatureLevel()`
|
||||
- engine: Add missing `Material::getReflectionMode()` method in Java
|
||||
- engine: Support basic usage of post-processing materials on feature level 0
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.43.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.45.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.43.1'
|
||||
pod 'Filament', '~> 1.45.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -128,3 +128,15 @@ Navigate to [Filament's release
|
||||
workflow](https://github.com/google/filament/actions/workflows/release.yml). Hit the _Run workflow_
|
||||
dropdown. Modify _Platform to build_ and _Release tag to build_, then hit _Run workflow_. This will
|
||||
initiate a new release run.
|
||||
|
||||
## 11. Kick off the npm and CocoaPods release jobs
|
||||
|
||||
Navigate to [Filament's npm deploy
|
||||
workflow](https://github.com/google/filament/actions/workflows/npm-deploy.yml).
|
||||
Hit the _Run workflow_ dropdown. Modify _Release tag to deploy_ to the tag corresponding to this
|
||||
release (for example, v1.42.2).
|
||||
|
||||
Navigate to [Filament's CocoaPods deploy
|
||||
workflow](https://github.com/google/filament/actions/workflows/cocopods-deploy.yml).
|
||||
Hit the _Run workflow_ dropdown. Modify _Release tag to deploy_ to the tag corresponding to this
|
||||
release (for example, v1.42.2).
|
||||
|
||||
@@ -7,6 +7,31 @@ 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.45.1
|
||||
|
||||
- engine: Added parameter for configuring JobSystem thread count
|
||||
- engine: In Java, introduce Engine.Builder
|
||||
- gltfio: fix ubershader index for transmission&volume material
|
||||
- engine: New tone mapper: `AgXTonemapper`.
|
||||
- matinfo: Add support for viewing ESSL 1.0 shaders
|
||||
- engine: Add `Renderer::getClearOptions()` [b/243846268]
|
||||
- engine: Fix stable shadows (again) when an IBL rotation is used
|
||||
|
||||
## v1.45.0
|
||||
|
||||
- materials: fix alpha masked materials when MSAA is turned on [⚠️ **Recompile materials**]
|
||||
- materials: better support materials with custom depth [**Recompile Materials**]
|
||||
- engine: fade shadows at shadowFar distance instead of hard cutoff [⚠️ **New Material Version**]
|
||||
|
||||
## v1.44.0
|
||||
|
||||
- engine: add support for skinning with more than four bones per vertex.
|
||||
- engine: remove `BloomOptions::anamorphism` which wasn't working well in most cases [**API CHANGE**]
|
||||
- engine: new API to return a Material's supported variants, C++ only (b/297456590)
|
||||
- build: fix emscripten-1.3.46 build
|
||||
- engine: materials built for feature level 0 can now also be loaded in higher feature levels [⚠️
|
||||
**New Material Version**]
|
||||
|
||||
## v1.43.1
|
||||
|
||||
## v1.43.0
|
||||
|
||||
@@ -6,9 +6,6 @@ option(FILAMENT_ENABLE_MATDBG "Enables Material debugger" OFF)
|
||||
set(FILAMENT_DIR ${FILAMENT_DIST_DIR})
|
||||
|
||||
set(FILAMAT_FLAVOR "filamat")
|
||||
if(FILAMAT_LITE)
|
||||
set(FILAMAT_FLAVOR "filamat_lite")
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
message("Library filamat ignores Vulkan settings")
|
||||
|
||||
@@ -1,29 +1,8 @@
|
||||
android {
|
||||
namespace 'com.google.android.filament.filamat'
|
||||
|
||||
flavorDimensions "functionality"
|
||||
productFlavors {
|
||||
full {
|
||||
dimension "functionality"
|
||||
}
|
||||
|
||||
lite {
|
||||
dimension "functionality"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments.add("-DFILAMAT_LITE=ON")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
singleVariant("fullRelease") {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
singleVariant("liteRelease") {
|
||||
singleVariant("release") {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
@@ -39,14 +18,9 @@ apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
|
||||
afterEvaluate { project ->
|
||||
publishing {
|
||||
publications {
|
||||
fullRelease(MavenPublication) {
|
||||
release(MavenPublication) {
|
||||
artifactId = POM_ARTIFACT_ID_FULL
|
||||
from components.fullRelease
|
||||
}
|
||||
|
||||
liteRelease(MavenPublication) {
|
||||
artifactId = POM_ARTIFACT_ID_LITE
|
||||
from components.liteRelease
|
||||
from components.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,8 @@
|
||||
using namespace filament;
|
||||
using namespace utils;
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Engine_nCreateEngine(JNIEnv*, jclass, jlong backend,
|
||||
jlong sharedContext) {
|
||||
return (jlong) Engine::create((Engine::Backend) backend, nullptr, (void*) sharedContext);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv*, jclass, jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
Engine::destroy(&engine);
|
||||
}
|
||||
@@ -454,4 +447,56 @@ Java_com_google_android_filament_Engine_nGetActiveFeatureLevel(JNIEnv *, jclass,
|
||||
jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jint)engine->getActiveFeatureLevel();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nCreateBuilder(JNIEnv*,
|
||||
jclass) {
|
||||
Engine::Builder* builder = new Engine::Builder{};
|
||||
return (jlong) builder;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nDestroyBuilder(JNIEnv*,
|
||||
jclass, jlong nativeBuilder) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
delete builder;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderBackend(
|
||||
JNIEnv*, jclass, jlong nativeBuilder, jlong backend) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
builder->backend((Engine::Backend) backend);
|
||||
}
|
||||
|
||||
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) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
|
||||
.perRenderPassArenaSizeMB = (uint32_t) perRenderPassArenaSizeMB,
|
||||
.driverHandleArenaSizeMB = (uint32_t) driverHandleArenaSizeMB,
|
||||
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
|
||||
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
|
||||
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
|
||||
};
|
||||
builder->config(&config);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderFeatureLevel(
|
||||
JNIEnv*, jclass, jlong nativeBuilder, jint ordinal) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
builder->featureLevel((Engine::FeatureLevel)ordinal);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderSharedContext(
|
||||
JNIEnv*, jclass, jlong nativeBuilder, jlong sharedContext) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
builder->sharedContext((void*) sharedContext);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
return (jlong) builder->build();
|
||||
}
|
||||
|
||||
@@ -105,6 +105,22 @@ Java_com_google_android_filament_Material_nGetRefractionType(JNIEnv*, jclass,
|
||||
return (jint) material->getRefractionType();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Material_nGetReflectionMode(JNIEnv*, jclass,
|
||||
jlong nativeMaterial) {
|
||||
Material* material = (Material*) nativeMaterial;
|
||||
return (jint) material->getReflectionMode();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Material_nGetFeatureLevel(JNIEnv*, jclass,
|
||||
jlong nativeMaterial) {
|
||||
Material* material = (Material*) nativeMaterial;
|
||||
return (jint) material->getFeatureLevel();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Material_nGetVertexDomain(JNIEnv*, jclass,
|
||||
|
||||
@@ -71,6 +71,13 @@ Java_com_google_android_filament_Scene_nRemoveEntities(JNIEnv *env, jclass type,
|
||||
env->ReleaseIntArrayElements(entities, (jint*) nativeEntities, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Scene_nGetEntityCount(JNIEnv *env, jclass type,
|
||||
jlong nativeScene) {
|
||||
Scene* scene = (Scene*) nativeScene;
|
||||
return (jint) scene->getEntityCount();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Scene_nGetRenderableCount(JNIEnv *env, jclass type,
|
||||
jlong nativeScene) {
|
||||
|
||||
@@ -47,6 +47,11 @@ Java_com_google_android_filament_ToneMapper_nCreateFilmicToneMapper(JNIEnv*, jcl
|
||||
return (jlong) new FilmicToneMapper();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) {
|
||||
return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_ToneMapper_nCreateGenericToneMapper(JNIEnv*, jclass,
|
||||
jfloat contrast, jfloat midGrayIn, jfloat midGrayOut, jfloat hdrMax) {
|
||||
|
||||
@@ -282,7 +282,7 @@ Java_com_google_android_filament_View_nSetSSCTOptions(JNIEnv *, jclass, jlong na
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass,
|
||||
jlong nativeView, jlong nativeTexture,
|
||||
jfloat dirtStrength, jfloat strength, jint resolution, jfloat anamorphism, jint levels,
|
||||
jfloat dirtStrength, jfloat strength, jint resolution, jint levels,
|
||||
jint blendMode, jboolean threshold, jboolean enabled, jfloat highlight,
|
||||
jboolean lensFlare, jboolean starburst, jfloat chromaticAberration, jint ghostCount,
|
||||
jfloat ghostSpacing, jfloat ghostThreshold, jfloat haloThickness, jfloat haloRadius,
|
||||
@@ -294,7 +294,6 @@ Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass,
|
||||
.dirtStrength = dirtStrength,
|
||||
.strength = strength,
|
||||
.resolution = (uint32_t)resolution,
|
||||
.anamorphism = anamorphism,
|
||||
.levels = (uint8_t)levels,
|
||||
.blendMode = (View::BloomOptions::BlendMode)blendMode,
|
||||
.threshold = (bool)threshold,
|
||||
|
||||
@@ -154,6 +154,195 @@ public class Engine {
|
||||
FEATURE_LEVEL_2
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs <code>Engine</code> objects using a builder pattern.
|
||||
*/
|
||||
public static class Builder {
|
||||
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
|
||||
private final BuilderFinalizer mFinalizer;
|
||||
private final long mNativeBuilder;
|
||||
|
||||
public Builder() {
|
||||
mNativeBuilder = nCreateBuilder();
|
||||
mFinalizer = new BuilderFinalizer(mNativeBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Backend} for the Engine.
|
||||
*
|
||||
* @param backend Driver backend to use
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
public Builder backend(Backend backend) {
|
||||
nSetBuilderBackend(mNativeBuilder, backend.ordinal());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sharedContext for the Engine.
|
||||
*
|
||||
* @param sharedContext A platform-dependant OpenGL context used as a shared context
|
||||
* when creating filament's internal context. On Android this parameter
|
||||
* <b>must be</b> an instance of {@link android.opengl.EGLContext}.
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
public Builder sharedContext(Object sharedContext) {
|
||||
if (Platform.get().validateSharedContext(sharedContext)) {
|
||||
nSetBuilderSharedContext(mNativeBuilder,
|
||||
Platform.get().getSharedContextNativeHandle(sharedContext));
|
||||
return this;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid shared context " + sharedContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the Engine with custom parameters.
|
||||
*
|
||||
* @param config A {@link Config} object
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
public Builder config(Config config) {
|
||||
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
|
||||
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
|
||||
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
|
||||
config.jobSystemThreadCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial featureLevel for the Engine.
|
||||
*
|
||||
* @param featureLevel The feature level at which initialize Filament.
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
*/
|
||||
public Builder featureLevel(FeatureLevel featureLevel) {
|
||||
nSetBuilderFeatureLevel(mNativeBuilder, featureLevel.ordinal());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of Engine
|
||||
*
|
||||
* @return A newly created <code>Engine</code>, or <code>null</code> if the GPU driver couldn't
|
||||
* be initialized, for instance if it doesn't support the right version of OpenGL or
|
||||
* OpenGL ES.
|
||||
*
|
||||
* @exception IllegalStateException can be thrown if there isn't enough memory to
|
||||
* allocate the command buffer.
|
||||
*/
|
||||
public Engine build() {
|
||||
long nativeEngine = nBuilderBuild(mNativeBuilder);
|
||||
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
|
||||
return new Engine(nativeEngine);
|
||||
}
|
||||
|
||||
private static class BuilderFinalizer {
|
||||
private final long mNativeObject;
|
||||
|
||||
BuilderFinalizer(long nativeObject) {
|
||||
mNativeObject = nativeObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalize() {
|
||||
try {
|
||||
super.finalize();
|
||||
} catch (Throwable t) { // Ignore
|
||||
} finally {
|
||||
nDestroyBuilder(mNativeObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for customizing the initialization of {@link Engine}.
|
||||
*/
|
||||
public static class Config {
|
||||
|
||||
// #defines in Engine.h
|
||||
private static final long FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB = 3;
|
||||
private static final long FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB = 2;
|
||||
private static final long FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB = 1;
|
||||
private static final long FILAMENT_COMMAND_BUFFER_SIZE_IN_MB =
|
||||
FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB * 3;
|
||||
|
||||
/**
|
||||
* Size in MiB of the low-level command buffer arena.
|
||||
*
|
||||
* Each new command buffer is allocated from here. If this buffer is too small the program
|
||||
* might terminate or rendering errors might occur.
|
||||
*
|
||||
* This is typically set to minCommandBufferSizeMB * 3, so that up to 3 frames can be
|
||||
* batched-up at once.
|
||||
*
|
||||
* This value affects the application's memory usage.
|
||||
*/
|
||||
public long commandBufferSizeMB = FILAMENT_COMMAND_BUFFER_SIZE_IN_MB;
|
||||
|
||||
/**
|
||||
* Size in MiB of the per-frame data arena.
|
||||
*
|
||||
* This is the main arena used for allocations when preparing a frame.
|
||||
* e.g.: Froxel data and high-level commands are allocated from this arena.
|
||||
*
|
||||
* If this size is too small, the program will abort on debug builds and have undefined
|
||||
* behavior otherwise.
|
||||
*
|
||||
* This value affects the application's memory usage.
|
||||
*/
|
||||
public long perRenderPassArenaSizeMB = FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB;
|
||||
|
||||
/**
|
||||
* Size in MiB of the backend's handle arena.
|
||||
*
|
||||
* Backends will fallback to slower heap-based allocations when running out of space and
|
||||
* log this condition.
|
||||
*
|
||||
* If 0, then the default value for the given platform is used
|
||||
*
|
||||
* This value affects the application's memory usage.
|
||||
*/
|
||||
public long driverHandleArenaSizeMB = 0;
|
||||
|
||||
/**
|
||||
* Minimum size in MiB of a low-level command buffer.
|
||||
*
|
||||
* This is how much space is guaranteed to be available for low-level commands when a new
|
||||
* buffer is allocated. If this is too small, the engine might have to stall to wait for
|
||||
* more space to become available, this situation is logged.
|
||||
*
|
||||
* This value does not affect the application's memory usage.
|
||||
*/
|
||||
public long minCommandBufferSizeMB = FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB;
|
||||
|
||||
/**
|
||||
* Size in MiB of the per-frame high level command buffer.
|
||||
*
|
||||
* This buffer is related to the number of draw calls achievable within a frame, if it is
|
||||
* too small, the program will abort on debug builds and have undefined behavior otherwise.
|
||||
*
|
||||
* It is allocated from the 'per-render-pass arena' above. Make sure that at least 1 MiB is
|
||||
* left in the per-render-pass arena when deciding the size of this buffer.
|
||||
*
|
||||
* This value does not affect the application's memory usage.
|
||||
*/
|
||||
public long perFrameCommandsSizeMB = FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB;
|
||||
|
||||
/**
|
||||
* Number of threads to use in Engine's JobSystem.
|
||||
*
|
||||
* Engine uses a utils::JobSystem to carry out paralleization of Engine workloads. This
|
||||
* value sets the number of threads allocated for JobSystem. Configuring this value can be
|
||||
* helpful in CPU-constrained environments where too many threads can cause contention of
|
||||
* CPU and reduce performance.
|
||||
*
|
||||
* The default value is 0, which implies that the Engine will use a heuristic to determine
|
||||
* the number of threads to use.
|
||||
*/
|
||||
public long jobSystemThreadCount = 0;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine) {
|
||||
mNativeObject = nativeEngine;
|
||||
mTransformManager = new TransformManager(nGetTransformManager(nativeEngine));
|
||||
@@ -177,9 +366,7 @@ public class Engine {
|
||||
*/
|
||||
@NonNull
|
||||
public static Engine create() {
|
||||
long nativeEngine = nCreateEngine(0, 0);
|
||||
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
|
||||
return new Engine(nativeEngine);
|
||||
return new Builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,9 +386,9 @@ public class Engine {
|
||||
*/
|
||||
@NonNull
|
||||
public static Engine create(@NonNull Backend backend) {
|
||||
long nativeEngine = nCreateEngine(backend.ordinal(), 0);
|
||||
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
|
||||
return new Engine(nativeEngine);
|
||||
return new Builder()
|
||||
.backend(backend)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,13 +410,9 @@ public class Engine {
|
||||
*/
|
||||
@NonNull
|
||||
public static Engine create(@NonNull Object sharedContext) {
|
||||
if (Platform.get().validateSharedContext(sharedContext)) {
|
||||
long nativeEngine = nCreateEngine(0,
|
||||
Platform.get().getSharedContextNativeHandle(sharedContext));
|
||||
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
|
||||
return new Engine(nativeEngine);
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid shared context " + sharedContext);
|
||||
return new Builder()
|
||||
.sharedContext(sharedContext)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,17 +479,23 @@ public class Engine {
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is
|
||||
* active. The selected feature level must not be higher than the value returned by
|
||||
* getActiveFeatureLevel() and it's not possible lower the active feature level.
|
||||
* Activate all features of a given feature level. If an explicit feature level is not specified
|
||||
* at Engine initialization time via {@link Builder#featureLevel}, the default feature level is
|
||||
* {@link FeatureLevel#FEATURE_LEVEL_0} on devices not compatible with GLES 3.0; otherwise, the
|
||||
* default is {@link FeatureLevel::FEATURE_LEVEL_1}. The selected feature level must not be
|
||||
* higher than the value returned by {@link #getActiveFeatureLevel} and it's not possible lower
|
||||
* the active feature level. Additionally, it is not possible to modify the feature level at all
|
||||
* if the Engine was initialized at {@link FeatureLevel#FEATURE_LEVEL_0}.
|
||||
*
|
||||
* @param featureLevel the feature level to activate. If featureLevel is lower than
|
||||
* getActiveFeatureLevel(), the current (higher) feature level is kept.
|
||||
* If featureLevel is higher than getSupportedFeatureLevel(), an exception
|
||||
* is thrown, or the program is terminated if exceptions are disabled.
|
||||
* @param featureLevel the feature level to activate. If featureLevel is lower than {@link
|
||||
* #getActiveFeatureLevel}, the current (higher) feature level is kept. If
|
||||
* featureLevel is higher than {@link #getSupportedFeatureLevel}, or if the
|
||||
* engine was initialized at feature level 0, an exception is thrown, or the
|
||||
* program is terminated if exceptions are disabled.
|
||||
*
|
||||
* @return the active feature level.
|
||||
*
|
||||
* @see Builder#featureLevel
|
||||
* @see #getSupportedFeatureLevel
|
||||
* @see #getActiveFeatureLevel
|
||||
*/
|
||||
@@ -914,7 +1103,6 @@ public class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
private static native long nCreateEngine(long backend, long sharedContext);
|
||||
private static native void nDestroyEngine(long nativeEngine);
|
||||
private static native long nGetBackend(long nativeEngine);
|
||||
private static native long nCreateSwapChain(long nativeEngine, Object nativeWindow, long flags);
|
||||
@@ -971,4 +1159,14 @@ public class Engine {
|
||||
private static native int nGetSupportedFeatureLevel(long nativeEngine);
|
||||
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
|
||||
private static native int nGetActiveFeatureLevel(long nativeEngine);
|
||||
|
||||
private static native long nCreateBuilder();
|
||||
private static native void nDestroyBuilder(long nativeBuilder);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Size;
|
||||
|
||||
import com.google.android.filament.proguard.UsedByNative;
|
||||
import com.google.android.filament.Engine.FeatureLevel;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.ArrayList;
|
||||
@@ -46,6 +47,8 @@ public class Material {
|
||||
static final BlendingMode[] sBlendingModeValues = BlendingMode.values();
|
||||
static final RefractionMode[] sRefractionModeValues = RefractionMode.values();
|
||||
static final RefractionType[] sRefractionTypeValues = RefractionType.values();
|
||||
static final ReflectionMode[] sReflectionModeValues = ReflectionMode.values();
|
||||
static final FeatureLevel[] sFeatureLevelValues = FeatureLevel.values();
|
||||
static final VertexDomain[] sVertexDomainValues = VertexDomain.values();
|
||||
static final CullingMode[] sCullingModeValues = CullingMode.values();
|
||||
static final VertexBuffer.VertexAttribute[] sVertexAttributeValues =
|
||||
@@ -181,6 +184,18 @@ public class Material {
|
||||
THIN
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported reflection modes
|
||||
*
|
||||
* @see
|
||||
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/lighting:reflections">
|
||||
* Lighting: reflections</a>
|
||||
*/
|
||||
public enum ReflectionMode {
|
||||
DEFAULT,
|
||||
SCREEN_SPACE
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported types of vertex domains
|
||||
*
|
||||
@@ -437,6 +452,28 @@ public class Material {
|
||||
return EnumCache.sRefractionTypeValues[nGetRefractionType(getNativeObject())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reflection mode of this material.
|
||||
*
|
||||
* @see
|
||||
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/lighting:reflections">
|
||||
* Lighting: reflections</a>
|
||||
*/
|
||||
public ReflectionMode getReflectionMode() {
|
||||
return EnumCache.sReflectionModeValues[nGetReflectionMode(getNativeObject())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum required feature level for this material.
|
||||
*
|
||||
* @see
|
||||
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/general:featurelevel">
|
||||
* General: featureLevel</a>
|
||||
*/
|
||||
public FeatureLevel getFeatureLevel() {
|
||||
return EnumCache.sFeatureLevelValues[nGetFeatureLevel(getNativeObject())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vertex domain of this material.
|
||||
*
|
||||
@@ -932,6 +969,8 @@ public class Material {
|
||||
private static native float nGetSpecularAntiAliasingThreshold(long nativeMaterial);
|
||||
private static native int nGetRefractionMode(long nativeMaterial);
|
||||
private static native int nGetRefractionType(long nativeMaterial);
|
||||
private static native int nGetReflectionMode(long nativeMaterial);
|
||||
private static native int nGetFeatureLevel(long nativeMaterial);
|
||||
|
||||
|
||||
private static native int nGetParameterCount(long nativeMaterial);
|
||||
|
||||
@@ -146,18 +146,29 @@ public class Scene {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of {@link RenderableManager} components in the <code>Scene</code>.
|
||||
* Returns the total number of Entities in the <code>Scene</code>, whether alive or not.
|
||||
*
|
||||
* @return number of {@link RenderableManager} components in the <code>Scene</code>..
|
||||
* @return the total number of Entities in the <code>Scene</code>.
|
||||
*/
|
||||
public int getEntityCount() {
|
||||
return nGetEntityCount(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of active (alive) {@link RenderableManager} components in the
|
||||
* <code>Scene</code>.
|
||||
*
|
||||
* @return number of {@link RenderableManager} components in the <code>Scene</code>.
|
||||
*/
|
||||
public int getRenderableCount() {
|
||||
return nGetRenderableCount(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of {@link LightManager} components in the <code>Scene</code>.
|
||||
* Returns the number of active (alive) {@link LightManager} components in the
|
||||
* <code>Scene</code>.
|
||||
*
|
||||
* @return number of {@link LightManager} components in the <code>Scene</code>..
|
||||
* @return number of {@link LightManager} components in the <code>Scene</code>.
|
||||
*/
|
||||
public int getLightCount() {
|
||||
return nGetLightCount(getNativeObject());
|
||||
@@ -189,6 +200,7 @@ public class Scene {
|
||||
private static native void nAddEntities(long nativeScene, int[] entities);
|
||||
private static native void nRemove(long nativeScene, int entity);
|
||||
private static native void nRemoveEntities(long nativeScene, int[] entities);
|
||||
private static native int nGetEntityCount(long nativeScene);
|
||||
private static native int nGetRenderableCount(long nativeScene);
|
||||
private static native int nGetLightCount(long nativeScene);
|
||||
private static native boolean nHasEntity(long nativeScene, int entity);
|
||||
|
||||
@@ -103,6 +103,31 @@ public class SwapChain {
|
||||
*/
|
||||
public static final long CONFIG_SRGB_COLORSPACE = 0x10;
|
||||
|
||||
/**
|
||||
* Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer.
|
||||
*
|
||||
* This flag is necessary when using View::setStencilBufferEnabled and rendering directly into
|
||||
* the SwapChain (when post-processing is disabled).
|
||||
*
|
||||
* The specific format of the stencil buffer depends on platform support. The following pixel
|
||||
* formats are tried, in order of preference:
|
||||
*
|
||||
* Depth only (without CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F
|
||||
* - DEPTH24
|
||||
*
|
||||
* Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F_STENCIL8
|
||||
* - DEPTH24F_STENCIL8
|
||||
*
|
||||
* Note that enabling the stencil buffer may hinder depth precision and should only be used if
|
||||
* necessary.
|
||||
*
|
||||
* @see View#setStencilBufferEnabled
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20;
|
||||
|
||||
SwapChain(long nativeSwapChain, Object surface) {
|
||||
mNativeObject = nativeSwapChain;
|
||||
mSurface = surface;
|
||||
|
||||
@@ -100,6 +100,45 @@ public class ToneMapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AgX tone mapping operator.
|
||||
*/
|
||||
public static class Agx extends ToneMapper {
|
||||
|
||||
public enum AgxLook {
|
||||
/**
|
||||
* Base contrast with no look applied
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* A punchy and more chroma laden look for sRGB displays
|
||||
*/
|
||||
PUNCHY,
|
||||
|
||||
/**
|
||||
* A golden tinted, slightly washed look for BT.1886 displays
|
||||
*/
|
||||
GOLDEN
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new AgX tone mapper with no look applied.
|
||||
*/
|
||||
public Agx() {
|
||||
this(AgxLook.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new AgX tone mapper.
|
||||
*
|
||||
* @param look: an optional creative adjustment to contrast and saturation
|
||||
*/
|
||||
public Agx(AgxLook look) {
|
||||
super(nCreateAgxToneMapper(look.ordinal()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic tone mapping operator that gives control over the tone mapping
|
||||
* curve. This operator can be used to control the aesthetics of the final
|
||||
@@ -194,6 +233,7 @@ public class ToneMapper {
|
||||
private static native long nCreateACESToneMapper();
|
||||
private static native long nCreateACESLegacyToneMapper();
|
||||
private static native long nCreateFilmicToneMapper();
|
||||
private static native long nCreateAgxToneMapper(int look);
|
||||
private static native long nCreateGenericToneMapper(
|
||||
float contrast, float midGrayIn, float midGrayOut, float hdrMax);
|
||||
|
||||
|
||||
@@ -904,7 +904,7 @@ public class View {
|
||||
mBloomOptions = options;
|
||||
nSetBloomOptions(getNativeObject(), options.dirt != null ? options.dirt.getNativeObject() : 0,
|
||||
options.dirtStrength, options.strength, options.resolution,
|
||||
options.anamorphism, options.levels, options.blendMode.ordinal(),
|
||||
options.levels, options.blendMode.ordinal(),
|
||||
options.threshold, options.enabled, options.highlight,
|
||||
options.lensFlare, options.starburst, options.chromaticAberration,
|
||||
options.ghostCount, options.ghostSpacing, options.ghostThreshold,
|
||||
@@ -1032,7 +1032,8 @@ public class View {
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Post-processing must be enabled in order to use the stencil buffer.
|
||||
* If post-processing is disabled, then the SwapChain must have the CONFIG_HAS_STENCIL_BUFFER
|
||||
* flag set in order to use the stencil buffer.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
@@ -1216,7 +1217,7 @@ public class View {
|
||||
private static native int nGetAmbientOcclusion(long nativeView);
|
||||
private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, float bilateralThreshold, int quality, int lowPassFilter, int upsampling, boolean enabled, boolean bentNormals, float minHorizonAngleRad);
|
||||
private static native void nSetSSCTOptions(long nativeView, float ssctLightConeRad, float ssctStartTraceDistance, float ssctContactDistanceMax, float ssctIntensity, float v, float v1, float v2, float ssctDepthBias, float ssctDepthSlopeBias, int ssctSampleCount, int ssctRayCount, boolean ssctEnabled);
|
||||
private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled, float highlight,
|
||||
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 nSetBlendMode(long nativeView, int blendMode);
|
||||
@@ -1353,8 +1354,6 @@ public class View {
|
||||
* blendMode: Whether the bloom effect is purely additive (false) or mixed with the original
|
||||
* image (true).
|
||||
*
|
||||
* anamorphism: Bloom's aspect ratio (x/y), for artistic purposes.
|
||||
*
|
||||
* threshold: When enabled, a threshold at 1.0 is applied on the source image, this is
|
||||
* useful for artistic reasons and is usually needed when a dirt texture is used.
|
||||
*
|
||||
@@ -1393,10 +1392,6 @@ public class View {
|
||||
* resolution of vertical axis (2^levels to 2048)
|
||||
*/
|
||||
public int resolution = 384;
|
||||
/**
|
||||
* bloom x/y aspect-ratio (1/32 to 32)
|
||||
*/
|
||||
public float anamorphism = 1.0f;
|
||||
/**
|
||||
* number of blur levels (1 to 11)
|
||||
*/
|
||||
@@ -1935,6 +1930,7 @@ public class View {
|
||||
* PCF with soft shadows and contact hardening
|
||||
*/
|
||||
PCSS,
|
||||
PCFd,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+49
-40
@@ -227,8 +227,10 @@ public class UiHelper {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
}
|
||||
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
|
||||
mRenderCallback.onResized(width, height);
|
||||
if (mRenderCallback != null) {
|
||||
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -298,7 +300,6 @@ public class UiHelper {
|
||||
|
||||
/**
|
||||
* Checks whether we are ready to render into the attached surface.
|
||||
*
|
||||
* Using OpenGL ES when this returns true, will result in drawing commands being lost,
|
||||
* HOWEVER, GLES state will be preserved. This is useful to initialize the engine.
|
||||
*
|
||||
@@ -343,7 +344,6 @@ public class UiHelper {
|
||||
/**
|
||||
* Controls whether the render target (SurfaceView or TextureView) is opaque or not.
|
||||
* The render target is considered opaque by default.
|
||||
*
|
||||
* Must be called before calling {@link #attachTo(SurfaceView)}, {@link #attachTo(TextureView)},
|
||||
* or {@link #attachTo(SurfaceHolder)}.
|
||||
*
|
||||
@@ -366,10 +366,8 @@ public class UiHelper {
|
||||
* positioned above other surfaces but below the activity's surface. This property
|
||||
* only has an effect when used in combination with {@link #setOpaque(boolean) setOpaque(false)}
|
||||
* and does not affect TextureView targets.
|
||||
*
|
||||
* Must be called before calling {@link #attachTo(SurfaceView)}
|
||||
* or {@link #attachTo(TextureView)}.
|
||||
*
|
||||
* Has no effect when using {@link #attachTo(SurfaceHolder)}.
|
||||
*
|
||||
* @param overlay Indicates whether the render target should be rendered below the activity's
|
||||
@@ -390,7 +388,6 @@ public class UiHelper {
|
||||
|
||||
/**
|
||||
* Associate UiHelper with a SurfaceView.
|
||||
*
|
||||
* As soon as SurfaceView is ready (i.e. has a Surface), we'll create the
|
||||
* EGL resources needed, and call user callbacks if needed.
|
||||
*/
|
||||
@@ -412,21 +409,23 @@ public class UiHelper {
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
SurfaceHolder holder, int format, int width, int height) {
|
||||
@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 + ")");
|
||||
mRenderCallback.onResized(width, height);
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
@@ -450,7 +449,6 @@ public class UiHelper {
|
||||
|
||||
/**
|
||||
* Associate UiHelper with a TextureView.
|
||||
*
|
||||
* As soon as TextureView is ready (i.e. has a buffer), we'll create the
|
||||
* EGL resources needed, and call user callbacks if needed.
|
||||
*/
|
||||
@@ -463,7 +461,7 @@ public class UiHelper {
|
||||
TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
SurfaceTexture surfaceTexture, int width, int height) {
|
||||
@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) {
|
||||
@@ -478,40 +476,44 @@ public class UiHelper {
|
||||
|
||||
createSwapChain(surface);
|
||||
|
||||
// Call this the first time because onSurfaceTextureSizeChanged()
|
||||
// isn't called at initialization time
|
||||
mRenderCallback.onResized(width, height);
|
||||
if (mRenderCallback != null) {
|
||||
// Call this the first time because onSurfaceTextureSizeChanged()
|
||||
// isn't called at initialization time
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
SurfaceTexture surfaceTexture, int width, int height) {
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
|
||||
} else {
|
||||
mRenderCallback.onResized(width, height);
|
||||
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());
|
||||
}
|
||||
// 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(SurfaceTexture surfaceTexture) {
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
|
||||
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { }
|
||||
};
|
||||
|
||||
view.setSurfaceTextureListener(listener);
|
||||
@@ -519,14 +521,15 @@ public class UiHelper {
|
||||
// in case the View's SurfaceTexture already existed
|
||||
if (view.isAvailable()) {
|
||||
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
|
||||
listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight);
|
||||
if (surfaceTexture != null) {
|
||||
listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate UiHelper with a SurfaceHolder.
|
||||
*
|
||||
* As soon as a Surface is created, we'll create the
|
||||
* EGL resources needed, and call user callbacks if needed.
|
||||
*/
|
||||
@@ -539,20 +542,22 @@ public class UiHelper {
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder surfaceHolder) {
|
||||
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
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 + ")");
|
||||
mRenderCallback.onResized(width, height);
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
@@ -587,7 +592,9 @@ public class UiHelper {
|
||||
}
|
||||
|
||||
private void createSwapChain(@NonNull Surface surface) {
|
||||
mRenderCallback.onNativeWindowChanged(surface);
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onNativeWindowChanged(surface);
|
||||
}
|
||||
mHasSwapChain = true;
|
||||
}
|
||||
|
||||
@@ -595,7 +602,9 @@ public class UiHelper {
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
}
|
||||
mRenderCallback.onDetachedFromSurface();
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onDetachedFromSurface();
|
||||
}
|
||||
mHasSwapChain = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
missingDimensionStrategy 'functionality', 'full'
|
||||
}
|
||||
packagingOptions {
|
||||
// No need to package up the following shared libs, which arise as a side effect of our
|
||||
// externalNativeBuild dependencies. When clients pick and choose from project-level gradle
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// Operators +, *, / based on http://half.sourceforge.net/ by Christian Rau
|
||||
// and licensed under MIT
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package com.google.android.filament.utils
|
||||
|
||||
import com.google.android.filament.utils.Half.Companion.POSITIVE_INFINITY
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
android {
|
||||
namespace 'com.google.android.filament.gltfio'
|
||||
|
||||
flavorDimensions "functionality"
|
||||
productFlavors {
|
||||
full {
|
||||
dimension "functionality"
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
// No need to package up the following shared libs, which arise as a side effect of our
|
||||
// externalNativeBuild dependencies. When clients pick and choose from project-level gradle
|
||||
@@ -18,7 +12,7 @@ android {
|
||||
}
|
||||
|
||||
publishing {
|
||||
singleVariant("fullRelease") {
|
||||
singleVariant("release") {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
@@ -36,9 +30,9 @@ apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
|
||||
afterEvaluate { project ->
|
||||
publishing {
|
||||
publications {
|
||||
fullRelease(MavenPublication) {
|
||||
release(MavenPublication) {
|
||||
artifactId = POM_ARTIFACT_ID_FULL
|
||||
from components.fullRelease
|
||||
from components.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.43.1
|
||||
VERSION_NAME=1.45.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ android {
|
||||
applicationId "com.google.android.filament.gltf"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion versions.targetSdk
|
||||
missingDimensionStrategy 'functionality', 'full'
|
||||
}
|
||||
|
||||
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
|
||||
|
||||
+4
-13
@@ -110,7 +110,7 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
private fun setupFilament() {
|
||||
engine = Engine.create()
|
||||
engine = Engine.Builder().featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build()
|
||||
renderer = engine.createRenderer()
|
||||
scene = engine.createScene()
|
||||
view = engine.createView()
|
||||
@@ -120,13 +120,8 @@ class MainActivity : Activity() {
|
||||
private fun setupView() {
|
||||
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
|
||||
|
||||
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
|
||||
// post-processing is not supported at feature level 0
|
||||
view.isPostProcessingEnabled = false
|
||||
} else {
|
||||
// NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
|
||||
// view.isPostProcessingEnabled = false
|
||||
}
|
||||
// post-processing is not supported at feature level 0
|
||||
view.isPostProcessingEnabled = false
|
||||
|
||||
// Tell the view which camera we want to use
|
||||
view.camera = camera
|
||||
@@ -160,11 +155,7 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
private fun loadMaterial() {
|
||||
var name = "materials/baked_color.filamat"
|
||||
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
|
||||
name = "materials/baked_color_es2.filamat"
|
||||
}
|
||||
readUncompressedAsset(name).let {
|
||||
readUncompressedAsset("materials/baked_color.filamat").let {
|
||||
material = Material.Builder().payload(it, it.remaining()).build(engine)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ material {
|
||||
],
|
||||
|
||||
// This material disables all lighting
|
||||
shadingModel : unlit
|
||||
shadingModel : unlit,
|
||||
featureLevel : 0
|
||||
}
|
||||
|
||||
fragment {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Simple unlit material that uses the colors associated with each vertex.
|
||||
//
|
||||
// This source material must be compiled to a binary material using the matc tool.
|
||||
// The command used to compile this material is:
|
||||
// matc -p mobile -a opengl -o app/src/main/assets/baked_color.filamat app/src/materials/baked_color.mat
|
||||
//
|
||||
// See build.gradle for an example of how to compile materials automatically
|
||||
// Please refer to the documentation for more information about matc and the materials system.
|
||||
|
||||
material {
|
||||
name : baked_color,
|
||||
|
||||
// Lists the required vertex attributes
|
||||
// Here we only need a color (RGBA)
|
||||
requires : [
|
||||
color
|
||||
],
|
||||
|
||||
// This material disables all lighting
|
||||
shadingModel : unlit,
|
||||
featureLevel : 0
|
||||
}
|
||||
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
// You must always call the prepareMaterial() function
|
||||
prepareMaterial(material);
|
||||
|
||||
// We set the material's color to the color interpolated from
|
||||
// the model's vertices
|
||||
material.baseColor = getColor();
|
||||
}
|
||||
}
|
||||
@@ -32,13 +32,6 @@ android {
|
||||
targetSdkVersion versions.targetSdk
|
||||
}
|
||||
|
||||
// The filamat library has two variants: full and lite. Here we default to the "full" variant.
|
||||
// Replace "full" with "lite" to use the filamat-lite variant, which has a smaller binary size
|
||||
// but comes with certain restrictions. See "Filamat Lite" in libs/filamat/README.md.
|
||||
defaultConfig {
|
||||
missingDimensionStrategy 'functionality', 'full'
|
||||
}
|
||||
|
||||
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
|
||||
// is not configuration-cache friendly yet; this is only useful for Play publication
|
||||
dependenciesInfo {
|
||||
|
||||
@@ -33,7 +33,6 @@ android {
|
||||
applicationId "com.google.android.filament.textured"
|
||||
minSdkVersion versions.minSdk
|
||||
targetSdkVersion versions.targetSdk
|
||||
missingDimensionStrategy 'functionality', 'full'
|
||||
}
|
||||
|
||||
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
|
||||
|
||||
@@ -191,6 +191,7 @@ function build_clean {
|
||||
rm -Rf android/filamat-android/build android/filamat-android/.externalNativeBuild android/filamat-android/.cxx
|
||||
rm -Rf android/gltfio-android/build android/gltfio-android/.externalNativeBuild android/gltfio-android/.cxx
|
||||
rm -Rf android/filament-utils-android/build android/filament-utils-android/.externalNativeBuild android/filament-utils-android/.cxx
|
||||
rm -f compile_commands.json
|
||||
}
|
||||
|
||||
function build_clean_aggressive {
|
||||
@@ -232,6 +233,8 @@ function build_desktop_target {
|
||||
${ASAN_UBSAN_OPTION} \
|
||||
${architectures} \
|
||||
../..
|
||||
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
fi
|
||||
${BUILD_COMMAND} ${build_targets}
|
||||
|
||||
@@ -287,6 +290,8 @@ function build_webgl_with_target {
|
||||
-DCMAKE_INSTALL_PREFIX="../webgl-${lc_target}/filament" \
|
||||
-DWEBGL=1 \
|
||||
../..
|
||||
ln -sf "out/cmake-webgl-${lc_target}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
${BUILD_COMMAND} ${BUILD_TARGETS}
|
||||
)
|
||||
fi
|
||||
@@ -359,6 +364,8 @@ function build_android_target {
|
||||
${MATOPT_OPTION} \
|
||||
${VULKAN_ANDROID_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
fi
|
||||
|
||||
# We must always install Android libraries to build the AAR
|
||||
@@ -495,13 +502,13 @@ function build_android {
|
||||
|
||||
if [[ "${INSTALL_COMMAND}" ]]; then
|
||||
echo "Installing out/filamat-android-debug.aar..."
|
||||
cp filamat-android/build/outputs/aar/filamat-android-full-debug.aar ../out/filamat-android-debug.aar
|
||||
cp filamat-android/build/outputs/aar/filamat-android-debug.aar ../out/filamat-android-debug.aar
|
||||
|
||||
echo "Installing out/filament-android-debug.aar..."
|
||||
cp filament-android/build/outputs/aar/filament-android-debug.aar ../out/
|
||||
|
||||
echo "Installing out/gltfio-android-debug.aar..."
|
||||
cp gltfio-android/build/outputs/aar/gltfio-android-full-debug.aar ../out/gltfio-android-debug.aar
|
||||
cp gltfio-android/build/outputs/aar/gltfio-android-debug.aar ../out/gltfio-android-debug.aar
|
||||
|
||||
echo "Installing out/filament-utils-android-debug.aar..."
|
||||
cp filament-utils-android/build/outputs/aar/filament-utils-android-debug.aar ../out/filament-utils-android-debug.aar
|
||||
@@ -544,14 +551,13 @@ function build_android {
|
||||
|
||||
if [[ "${INSTALL_COMMAND}" ]]; then
|
||||
echo "Installing out/filamat-android-release.aar..."
|
||||
cp filamat-android/build/outputs/aar/filamat-android-lite-release.aar ../out/
|
||||
cp filamat-android/build/outputs/aar/filamat-android-full-release.aar ../out/filamat-android-release.aar
|
||||
cp filamat-android/build/outputs/aar/filamat-android-release.aar ../out/filamat-android-release.aar
|
||||
|
||||
echo "Installing out/filament-android-release.aar..."
|
||||
cp filament-android/build/outputs/aar/filament-android-release.aar ../out/
|
||||
|
||||
echo "Installing out/gltfio-android-release.aar..."
|
||||
cp gltfio-android/build/outputs/aar/gltfio-android-full-release.aar ../out/gltfio-android-release.aar
|
||||
cp gltfio-android/build/outputs/aar/gltfio-android-release.aar ../out/gltfio-android-release.aar
|
||||
|
||||
echo "Installing out/filament-utils-android-release.aar..."
|
||||
cp filament-utils-android/build/outputs/aar/filament-utils-android-release.aar ../out/filament-utils-android-release.aar
|
||||
@@ -592,6 +598,8 @@ function build_ios_target {
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
fi
|
||||
|
||||
${BUILD_COMMAND}
|
||||
@@ -752,7 +760,10 @@ function run_tests {
|
||||
}
|
||||
|
||||
function check_debug_release_build {
|
||||
if [[ "${ISSUE_DEBUG_BUILD}" == "true" || "${ISSUE_RELEASE_BUILD}" == "true" ]]; then
|
||||
if [[ "${ISSUE_DEBUG_BUILD}" == "true" || \
|
||||
"${ISSUE_RELEASE_BUILD}" == "true" || \
|
||||
"${ISSUE_CLEAN}" == "true" || \
|
||||
"${ISSUE_WEB_DOCS}" == "true" ]]; then
|
||||
"$@";
|
||||
else
|
||||
echo "You must declare a debug or release target for $@ builds."
|
||||
|
||||
@@ -5,7 +5,6 @@ libs/math/test_math
|
||||
libs/image/test_image compare libs/image/tests/reference/
|
||||
libs/utils/test_utils
|
||||
libs/filamat/test_filamat
|
||||
libs/filamat/test_filamat_lite
|
||||
tools/matc/test_matc
|
||||
tools/cmgen/test_cmgen compare
|
||||
tools/glslminifier/test_glslminifier
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
// If you are bundling this with rollup, webpack, or esbuild, the following URL should be trimmed.
|
||||
import { LitElement, html, css } from "https://unpkg.com/lit?module";
|
||||
import { LitElement, html, css } from "https://unpkg.com/lit@2.8.0?module";
|
||||
|
||||
// This little utility checks if the Filament module is ready for action.
|
||||
// If so, it immediately calls the given function. If not, it asks the Filament
|
||||
@@ -287,12 +287,12 @@ class FilamentViewer extends LitElement {
|
||||
// Dropping a glb file is simple because there are no external resources.
|
||||
if (this.srcBlob && this.srcBlob.name.endsWith(".glb")) {
|
||||
this.srcBlob.arrayBuffer().then(buffer => {
|
||||
this.asset = this.loader.createAssetFromBinary(new Uint8Array(buffer));
|
||||
this.asset = this.loader.createAsset(new Uint8Array(buffer));
|
||||
const aabb = this.asset.getBoundingBox();
|
||||
this.assetRoot = this.asset.getRoot();
|
||||
this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
|
||||
this.asset.loadResources();
|
||||
this.animator = this.asset.getAnimator();
|
||||
this.animator = this.asset.getInstance().getAnimator();
|
||||
this.animationStartTime = Date.now();
|
||||
this._updateOverlay();
|
||||
});
|
||||
@@ -304,8 +304,6 @@ class FilamentViewer extends LitElement {
|
||||
|
||||
const config = {
|
||||
normalizeSkinningWeights: true,
|
||||
recomputeBoundingBoxes: false,
|
||||
ignoreBindTransform: false,
|
||||
asyncInterval: 30
|
||||
};
|
||||
|
||||
@@ -320,22 +318,20 @@ class FilamentViewer extends LitElement {
|
||||
resourceLoader.delete();
|
||||
stbProvider.delete();
|
||||
ktx2Provider.delete();
|
||||
this.animator = this.asset.getAnimator();
|
||||
this.animator = this.asset.getInstance().getAnimator();
|
||||
this.animationStartTime = Date.now();
|
||||
}
|
||||
}, config.asyncInterval);
|
||||
};
|
||||
|
||||
this.srcBlob.arrayBuffer().then(buffer => {
|
||||
this.asset = this.loader.createAssetFromJson(new Uint8Array(buffer));
|
||||
this.asset = this.loader.createAsset(new Uint8Array(buffer));
|
||||
const aabb = this.asset.getBoundingBox();
|
||||
this.assetRoot = this.asset.getRoot();
|
||||
this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
|
||||
|
||||
const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine,
|
||||
config.normalizeSkinningWeights,
|
||||
config.recomputeBoundingBoxes,
|
||||
config.ignoreBindTransform);
|
||||
config.normalizeSkinningWeights);
|
||||
|
||||
const stbProvider = new Filament.gltfio$StbProvider(this.engine);
|
||||
const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine);
|
||||
@@ -367,12 +363,7 @@ class FilamentViewer extends LitElement {
|
||||
return response.arrayBuffer();
|
||||
}).then(arrayBuffer => {
|
||||
const modelData = new Uint8Array(arrayBuffer);
|
||||
if (this.src.endsWith(".glb")) {
|
||||
this.asset = this.loader.createAssetFromBinary(modelData);
|
||||
} else {
|
||||
this.asset = this.loader.createAssetFromJson(modelData);
|
||||
}
|
||||
|
||||
this.asset = this.loader.createAsset(modelData);
|
||||
const aabb = this.asset.getBoundingBox();
|
||||
this.assetRoot = this.asset.getRoot();
|
||||
this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
|
||||
@@ -380,7 +371,7 @@ class FilamentViewer extends LitElement {
|
||||
const basePath = '' + new URL(this.src, document.location);
|
||||
|
||||
this.asset.loadResources(() => {
|
||||
this.animator = this.asset.getAnimator();
|
||||
this.animator = this.asset.getInstance().getAnimator();
|
||||
this.animationStartTime = Date.now();
|
||||
this._applyMaterialVariant();
|
||||
}, null, basePath);
|
||||
@@ -441,14 +432,15 @@ class FilamentViewer extends LitElement {
|
||||
if (!this.hasAttribute("materialVariant")) {
|
||||
return;
|
||||
}
|
||||
const names = this.asset.getMaterialVariantNames();
|
||||
const instance = this.asset.getInstance();
|
||||
const names = instance.getMaterialVariantNames();
|
||||
const index = this.materialVariant;
|
||||
if (index < 0 || index >= names.length) {
|
||||
console.error(`Material variant ${index} does not exist in this asset.`);
|
||||
return;
|
||||
}
|
||||
console.info(this.src, `Applying material variant: ${names[index]}`);
|
||||
this.asset.applyMaterialVariant(index);
|
||||
instance.applyMaterialVariant(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ filament-viewer::part(canvas) {
|
||||
</p>
|
||||
|
||||
</main>
|
||||
<script src="https://unpkg.com/filament@1.25.3/filament.js"></script>
|
||||
<script src="https://unpkg.com/filament@1.44.0/filament.js"></script>
|
||||
<script src="https://unpkg.com/gltumble"></script>
|
||||
<script src="filament-viewer.js" type="module"></script>
|
||||
</body>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -22,27 +22,26 @@ converting <a href="https://github.com/google/filament/blob/main/assets/models/m
|
||||
<p>Next, let's create mipmapped KTX files using filament's <code>mipgen</code> tool. We'll create compressed and
|
||||
non-compressed variants for each texture, since not all platforms support the same compression
|
||||
formats. First copy over the PNG files from the <a href="https://github.com/google/filament/blob/main/assets/models/monkey">monkey folder</a>, then do:</p>
|
||||
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic"># Create mipmaps for base color and two compressed variants.</span>
|
||||
mipgen albedo.png albedo.ktx
|
||||
mipgen --compression<span style="color: #666666">=</span>astc_fast_ldr_4x4 albedo.png albedo_astc.ktx
|
||||
mipgen --compression<span style="color: #666666">=</span>s3tc_rgb_dxt1 albedo.png albedo_s3tc_srgb.ktx
|
||||
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic"># Create mipmaps for base color</span>
|
||||
mipgen albedo.png albedo.ktx2
|
||||
mipgen --compression<span style="color: #666666">=</span>uastc albedo.png albedo.ktx2
|
||||
|
||||
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the normal map and a compressed variant.</span>
|
||||
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear normal.png normal.ktx
|
||||
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear --compression<span style="color: #666666">=</span>etc_rgb8_normalxyz_40 <span style="color: #AA5D1F; font-weight: bold">\</span>
|
||||
normal.png normal_etc.ktx
|
||||
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear --compression<span style="color: #666666">=</span>uastc_normals <span style="color: #AA5D1F; font-weight: bold">\</span>
|
||||
normal.png normal.ktx2
|
||||
|
||||
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component roughness map and a compressed variant.</span>
|
||||
mipgen --grayscale roughness.png roughness.ktx
|
||||
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 roughness.png roughness_etc.ktx
|
||||
mipgen --grayscale --compression<span style="color: #666666">=</span>uastc roughness.png roughness.ktx2
|
||||
|
||||
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component metallic map and a compressed variant.</span>
|
||||
mipgen --grayscale metallic.png metallic.ktx
|
||||
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 metallic.png metallic_etc.ktx
|
||||
mipgen --grayscale --compression<span style="color: #666666">=</span>uastc metallic.png metallic.ktx2
|
||||
|
||||
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component occlusion map and a compressed variant.</span>
|
||||
mipgen --grayscale ao.png ao.ktx
|
||||
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 ao.png ao_etc.ktx
|
||||
mipgen --grayscale --compression<span style="color: #666666">=</span>uastc ao.png ao.ktx2
|
||||
</pre></div>
|
||||
|
||||
<p>For more information on mipgen's arguments and supported formats, do <code>mipgen --help</code>.</p>
|
||||
@@ -207,11 +206,11 @@ when the assets have finished downloading.</p>
|
||||
recreate the skybox using a higher-resolution texture. As a last step we unhide the renderable that
|
||||
was created in the app constructor.</p>
|
||||
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>Filament.fetch([sky_large_url,<span style="color: #bbbbbb"> </span>albedo_url,<span style="color: #bbbbbb"> </span>roughness_url,<span style="color: #bbbbbb"> </span>metallic_url,<span style="color: #bbbbbb"> </span>normal_url,<span style="color: #bbbbbb"> </span>ao_url],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=><span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(albedo_url,<span style="color: #bbbbbb"> </span>{srgb<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(roughness_url);<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(metallic_url);<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(normal_url);<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(ao_url);<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(albedo_url,<span style="color: #bbbbbb"> </span>{srgb<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(roughness_url);<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(metallic_url);<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(normal_url);<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(ao_url);<span style="color: #bbbbbb"></span>
|
||||
|
||||
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sampler<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.TextureSampler(<span style="color: #bbbbbb"></span>
|
||||
<span style="color: #bbbbbb"> </span>Filament.MinFilter.LINEAR_MIPMAP_LINEAR,<span style="color: #bbbbbb"></span>
|
||||
|
||||
@@ -33,13 +33,13 @@ class App {
|
||||
});
|
||||
Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url],
|
||||
() => {
|
||||
const albedo = this.engine.createTextureFromKtx1(albedo_url, {
|
||||
const albedo = this.engine.createTextureFromKtx2(albedo_url, {
|
||||
srgb: true
|
||||
});
|
||||
const roughness = this.engine.createTextureFromKtx1(roughness_url);
|
||||
const metallic = this.engine.createTextureFromKtx1(metallic_url);
|
||||
const normal = this.engine.createTextureFromKtx1(normal_url);
|
||||
const ao = this.engine.createTextureFromKtx1(ao_url);
|
||||
const roughness = this.engine.createTextureFromKtx2(roughness_url);
|
||||
const metallic = this.engine.createTextureFromKtx2(metallic_url);
|
||||
const normal = this.engine.createTextureFromKtx2(normal_url);
|
||||
const ao = this.engine.createTextureFromKtx2(ao_url);
|
||||
const sampler = new Filament.TextureSampler(Filament.MinFilter.LINEAR_MIPMAP_LINEAR,
|
||||
Filament.MagFilter.LINEAR, Filament.WrapMode.CLAMP_TO_EDGE);
|
||||
this.matinstance.setTextureParameter('albedo', albedo, sampler);
|
||||
|
||||
+3
-23
@@ -218,6 +218,7 @@ set(MATERIAL_SRCS
|
||||
src/materials/colorGrading/customResolveAsSubpass.mat
|
||||
src/materials/debugShadowCascades.mat
|
||||
src/materials/defaultMaterial.mat
|
||||
src/materials/defaultMaterial0.mat
|
||||
src/materials/dof/dof.mat
|
||||
src/materials/dof/dofCoc.mat
|
||||
src/materials/dof/dofDownsample.mat
|
||||
@@ -237,6 +238,7 @@ set(MATERIAL_SRCS
|
||||
src/materials/ssao/bilateralBlurBentNormals.mat
|
||||
src/materials/ssao/mipmapDepth.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/skybox0.mat
|
||||
src/materials/ssao/sao.mat
|
||||
src/materials/ssao/saoBentNormals.mat
|
||||
src/materials/separableGaussianBlur.mat
|
||||
@@ -245,11 +247,6 @@ set(MATERIAL_SRCS
|
||||
src/materials/vsmMipmap.mat
|
||||
)
|
||||
|
||||
set(MATERIAL_ES2_SRCS
|
||||
src/materials/defaultMaterial0.mat
|
||||
src/materials/skybox0.mat
|
||||
)
|
||||
|
||||
# Embed the binary resource blob for materials.
|
||||
get_resgen_vars(${RESOURCE_DIR} materials)
|
||||
list(APPEND PRIVATE_HDRS ${RESGEN_HEADER})
|
||||
@@ -315,23 +312,6 @@ foreach (mat_src ${MATERIAL_SRCS})
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach()
|
||||
|
||||
if (IS_MOBILE_TARGET AND FILAMENT_SUPPORTS_OPENGL)
|
||||
foreach (mat_src ${MATERIAL_ES2_SRCS})
|
||||
get_filename_component(localname "${mat_src}" NAME_WE)
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
set(output_path "${MATERIAL_DIR}/${localname}.filamat")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path}
|
||||
COMMAND matc -a opengl -p ${MATC_TARGET} ${MATC_OPT_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
)
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach ()
|
||||
endif ()
|
||||
|
||||
# Additional dependencies on included files for materials
|
||||
|
||||
add_custom_command(
|
||||
@@ -557,7 +537,7 @@ if (MSVC)
|
||||
set(FILAMENT_WARNINGS /W3)
|
||||
else()
|
||||
set(FILAMENT_WARNINGS
|
||||
-Wall -Wextra -Wno-unused-parameter
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
|
||||
-Wextra-semi -Wnewline-eof -Wdeprecated -Wundef
|
||||
-Wgnu-conditional-omitted-operand
|
||||
-Wweak-vtables -Wnon-virtual-dtor -Wclass-varargs -Wimplicit-fallthrough
|
||||
|
||||
@@ -27,6 +27,7 @@ set(SRCS
|
||||
src/BackendUtils.cpp
|
||||
src/BlobCacheKey.cpp
|
||||
src/Callable.cpp
|
||||
src/CallbackManager.cpp
|
||||
src/CircularBuffer.cpp
|
||||
src/CommandBufferQueue.cpp
|
||||
src/CommandStream.cpp
|
||||
@@ -54,6 +55,7 @@ set(PRIVATE_HDRS
|
||||
include/private/backend/HandleAllocator.h
|
||||
include/private/backend/PlatformFactory.h
|
||||
include/private/backend/SamplerGroup.h
|
||||
src/CallbackManager.h
|
||||
src/CommandStreamDispatcher.h
|
||||
src/CompilerThreadPool.h
|
||||
src/DataReshaper.h
|
||||
@@ -67,8 +69,6 @@ set(PRIVATE_HDRS
|
||||
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/OpenGLPlatform.h
|
||||
src/opengl/CallbackManager.h
|
||||
src/opengl/CallbackManager.cpp
|
||||
src/opengl/gl_headers.cpp
|
||||
src/opengl/gl_headers.h
|
||||
src/opengl/GLUtils.cpp
|
||||
@@ -133,6 +133,7 @@ if (FILAMENT_SUPPORTS_METAL)
|
||||
src/metal/MetalExternalImage.mm
|
||||
src/metal/MetalHandles.mm
|
||||
src/metal/MetalPlatform.mm
|
||||
src/metal/MetalShaderCompiler.mm
|
||||
src/metal/MetalState.mm
|
||||
src/metal/MetalTimerQuery.mm
|
||||
src/metal/MetalUtils.mm
|
||||
@@ -355,7 +356,7 @@ if (MSVC)
|
||||
set(FILAMENT_WARNINGS /W3)
|
||||
else()
|
||||
set(FILAMENT_WARNINGS
|
||||
-Wall -Wextra -Wno-unused-parameter
|
||||
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
|
||||
-Wextra-semi -Wnewline-eof -Wdeprecated -Wundef
|
||||
-Wgnu-conditional-omitted-operand
|
||||
-Wweak-vtables -Wnon-virtual-dtor -Wclass-varargs -Wimplicit-fallthrough
|
||||
|
||||
@@ -77,6 +77,11 @@ static constexpr uint64_t SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER = 0x8;
|
||||
*/
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_SRGB_COLORSPACE = 0x10;
|
||||
|
||||
/**
|
||||
* Indicates that the SwapChain should also contain a stencil component.
|
||||
*/
|
||||
static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = 0x20;
|
||||
|
||||
|
||||
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3.
|
||||
@@ -138,6 +143,30 @@ static constexpr const char* backendToString(Backend backend) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the shader language. Similar to the above backend enum, but the OpenGL backend can select
|
||||
* between two shader languages: ESSL 1.0 and ESSL 3.0.
|
||||
*/
|
||||
enum class ShaderLanguage {
|
||||
ESSL1 = 0,
|
||||
ESSL3 = 1,
|
||||
SPIRV = 2,
|
||||
MSL = 3,
|
||||
};
|
||||
|
||||
static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) {
|
||||
switch (shaderLanguage) {
|
||||
case ShaderLanguage::ESSL1:
|
||||
return "ESSL 1.0";
|
||||
case ShaderLanguage::ESSL3:
|
||||
return "ESSL 3.0";
|
||||
case ShaderLanguage::SPIRV:
|
||||
return "SPIR-V";
|
||||
case ShaderLanguage::MSL:
|
||||
return "MSL";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitmask for selecting render buffers
|
||||
*/
|
||||
@@ -327,6 +356,13 @@ enum class SamplerFormat : uint8_t {
|
||||
SHADOW = 3 //!< shadow sampler (PCF)
|
||||
};
|
||||
|
||||
//! Texture color space
|
||||
enum class SamplerTransferFunction : uint8_t {
|
||||
UNDEFINED = 0, //!< don't care
|
||||
IDENTITY = 1, //!< sampled as-is
|
||||
SRGB_TO_LINEAR = 2, //!< sampled as sRGB and converted to linear
|
||||
};
|
||||
|
||||
/**
|
||||
* Supported element types
|
||||
*/
|
||||
|
||||
@@ -46,6 +46,13 @@ public:
|
||||
* Driver clamps to valid values.
|
||||
*/
|
||||
size_t handleArenaSize = 0;
|
||||
|
||||
/*
|
||||
* this number of most-recently destroyed textures will be tracked for use-after-free.
|
||||
* Throws an exception when a texture is freed but still bound to a SamplerGroup and used in
|
||||
* a draw call. 0 disables completely. Currently only respected by the Metal backend.
|
||||
*/
|
||||
size_t textureUseAfterFreePoolSize = 0;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
@@ -107,6 +114,7 @@ public:
|
||||
* Platform. The <insert> and <retrieve> Invocables may be called at any time and
|
||||
* from any thread from the time at which setBlobFunc is called until the time that Platform
|
||||
* is destroyed. Concurrent calls to these functions from different threads is also allowed.
|
||||
* Either function can be null.
|
||||
*
|
||||
* @param insertBlob an Invocable that inserts a new value into the cache and associates
|
||||
* it with the given key
|
||||
@@ -116,9 +124,21 @@ public:
|
||||
void setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept;
|
||||
|
||||
/**
|
||||
* @return true if setBlobFunc was called.
|
||||
* @return true if insertBlob is valid.
|
||||
*/
|
||||
bool hasBlobFunc() const noexcept;
|
||||
bool hasInsertBlobFunc() const noexcept;
|
||||
|
||||
/**
|
||||
* @return true if retrieveBlob is valid.
|
||||
*/
|
||||
bool hasRetrieveBlobFunc() const noexcept;
|
||||
|
||||
/**
|
||||
* @return true if either of insertBlob or retrieveBlob are valid.
|
||||
*/
|
||||
bool hasBlobFunc() const noexcept {
|
||||
return hasInsertBlobFunc() || hasRetrieveBlobFunc();
|
||||
}
|
||||
|
||||
/**
|
||||
* To insert a new binary value into the cache and associate it with a given
|
||||
|
||||
@@ -42,6 +42,9 @@ public:
|
||||
void createContext(bool shared) override;
|
||||
void releaseContext() noexcept override;
|
||||
|
||||
// Return true if we're on an OpenGL platform (as opposed to OpenGL ES). false by default.
|
||||
virtual bool isOpenGL() const noexcept;
|
||||
|
||||
protected:
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
@@ -126,6 +129,7 @@ protected:
|
||||
EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE;
|
||||
EGLSurface mCurrentReadSurface = EGL_NO_SURFACE;
|
||||
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
|
||||
// mEGLConfig is valid only if ext.egl.KHR_no_config_context is false
|
||||
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
|
||||
Config mContextAttribs;
|
||||
std::vector<EGLContext> mAdditionalContexts;
|
||||
@@ -146,8 +150,8 @@ protected:
|
||||
|
||||
void initializeGlExtensions() noexcept;
|
||||
|
||||
private:
|
||||
EGLConfig findSwapChainConfig(uint64_t flags) const;
|
||||
protected:
|
||||
EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -30,6 +30,9 @@ public:
|
||||
|
||||
Driver* createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept override;
|
||||
|
||||
protected:
|
||||
bool isOpenGL() const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
|
||||
@@ -89,36 +90,44 @@ public:
|
||||
|
||||
// ----------------------------------------------------
|
||||
// ---------- Platform Customization options ----------
|
||||
/**
|
||||
* The client preference can be stored within the struct. We allow for two specification of
|
||||
* preference:
|
||||
* 1) A substring to match against `VkPhysicalDeviceProperties.deviceName`.
|
||||
* 2) Index of the device in the list as returned by vkEnumeratePhysicalDevices.
|
||||
*/
|
||||
struct GPUPreference {
|
||||
std::string deviceName;
|
||||
int8_t index = -1;
|
||||
struct Customization {
|
||||
/**
|
||||
* The client can specify the GPU (i.e. VkDevice) for the platform. We allow the
|
||||
* following preferences:
|
||||
* 1) A substring to match against `VkPhysicalDeviceProperties.deviceName`. Empty string
|
||||
* by default.
|
||||
* 2) Index of the device in the list as returned by
|
||||
* `vkEnumeratePhysicalDevices`. -1 by default to indicate no preference.
|
||||
*/
|
||||
struct GPUPreference {
|
||||
utils::CString deviceName;
|
||||
int8_t index = -1;
|
||||
} gpu;
|
||||
|
||||
/**
|
||||
* Whether the platform supports sRGB swapchain. Default is true.
|
||||
*/
|
||||
bool isSRGBSwapChainSupported = true;
|
||||
|
||||
/**
|
||||
* When the platform window is resized, we will flush and wait on the command queues
|
||||
* before recreating the swapchain. Default is true.
|
||||
*/
|
||||
bool flushAndWaitOnWindowResize = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client can provide a preference over the GPU to use in the vulkan instance
|
||||
* @return `GPUPreference` struct that indicates the client's preference
|
||||
* Client can override to indicate customized behavior or parameter for their platform.
|
||||
* @return `Customization` struct that indicates the client's platform
|
||||
* customizations.
|
||||
*/
|
||||
virtual GPUPreference getPreferredGPU() noexcept {
|
||||
virtual Customization getCustomization() const noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
// -------- End platform customization options --------
|
||||
// ----------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns whether the platform supports sRGB swapchain. This is true by default, and the client
|
||||
* needs to override this method to specify otherwise.
|
||||
* @return Whether the platform supports sRGB swapchain.
|
||||
*/
|
||||
virtual bool isSRGBSwapChainSupported() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the images handles and format of the memory backing the swapchain. This should be called
|
||||
* after createSwapChain() or after recreateIfResized().
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <backend/PipelineState.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -219,7 +219,7 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture,
|
||||
backend::TextureUsage, usage)
|
||||
|
||||
DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup,
|
||||
uint32_t, size)
|
||||
uint32_t, size, utils::FixedSizeString<32>, debugName)
|
||||
|
||||
DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive,
|
||||
backend::VertexBufferHandle, vbh,
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<16, 64, 208>
|
||||
#define HandleAllocatorVK HandleAllocator<16, 64, 880>
|
||||
#define HandleAllocatorMTL HandleAllocator<16, 64, 576>
|
||||
#define HandleAllocatorMTL HandleAllocator<16, 64, 584>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -239,14 +239,16 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// FIXME: We should be using a Spinlock here, at least on platforms where mutexes are not
|
||||
// efficient (i.e. non-Linux). However, we've seen some hangs on that spinlock, which
|
||||
// we don't understand well (b/308029108).
|
||||
#ifndef NDEBUG
|
||||
using HandleArena = utils::Arena<Allocator,
|
||||
utils::LockingPolicy::SpinLock,
|
||||
utils::LockingPolicy::Mutex,
|
||||
utils::TrackingPolicy::DebugAndHighWatermark>;
|
||||
#else
|
||||
using HandleArena = utils::Arena<Allocator,
|
||||
utils::LockingPolicy::SpinLock>;
|
||||
utils::LockingPolicy::Mutex>;
|
||||
#endif
|
||||
|
||||
// allocateHandle()/deallocateHandle() selects the pool to use at compile-time based on the
|
||||
@@ -256,6 +258,7 @@ private:
|
||||
HandleBase::HandleId allocateHandle() noexcept {
|
||||
if constexpr (SIZE <= P0) { return allocateHandleInPool<P0>(); }
|
||||
if constexpr (SIZE <= P1) { return allocateHandleInPool<P1>(); }
|
||||
static_assert(SIZE <= P2);
|
||||
return allocateHandleInPool<P2>();
|
||||
}
|
||||
|
||||
@@ -266,6 +269,7 @@ private:
|
||||
} else if constexpr (SIZE <= P1) {
|
||||
deallocateHandleFromPool<P1>(id);
|
||||
} else {
|
||||
static_assert(SIZE <= P2);
|
||||
deallocateHandleFromPool<P2>(id);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -12,10 +12,11 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
|
||||
#ifndef TNT_FILAMENT_BACKEND_CALLBACKMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_CALLBACKMANAGER_H
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
|
||||
@@ -95,4 +96,4 @@ private:
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
|
||||
#endif // TNT_FILAMENT_BACKEND_CALLBACKMANAGER_H
|
||||
@@ -28,14 +28,16 @@ bool Platform::pumpEvents() noexcept {
|
||||
}
|
||||
|
||||
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
|
||||
if (!mInsertBlob && !mRetrieveBlob) {
|
||||
mInsertBlob = std::move(insertBlob);
|
||||
mRetrieveBlob = std::move(retrieveBlob);
|
||||
}
|
||||
mInsertBlob = std::move(insertBlob);
|
||||
mRetrieveBlob = std::move(retrieveBlob);
|
||||
}
|
||||
|
||||
bool Platform::hasBlobFunc() const noexcept {
|
||||
return mInsertBlob && mRetrieveBlob;
|
||||
bool Platform::hasInsertBlobFunc() const noexcept {
|
||||
return bool(mInsertBlob);
|
||||
}
|
||||
|
||||
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
||||
return bool(mRetrieveBlob);
|
||||
}
|
||||
|
||||
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
#define TNT_METALCONTEXT_H
|
||||
|
||||
#include "MetalResourceTracker.h"
|
||||
#include "MetalShaderCompiler.h"
|
||||
#include "MetalState.h"
|
||||
|
||||
#include <CoreVideo/CVMetalTextureCache.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
#include <utils/FixedCircularBuffer.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <stack>
|
||||
@@ -53,6 +56,9 @@ struct MetalVertexBuffer;
|
||||
constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples
|
||||
|
||||
struct MetalContext {
|
||||
explicit MetalContext(size_t metalFreedTextureListSize)
|
||||
: texturesToDestroy(metalFreedTextureListSize) {}
|
||||
|
||||
MetalDriver* driver;
|
||||
id<MTLDevice> device = nullptr;
|
||||
id<MTLCommandQueue> commandQueue = nullptr;
|
||||
@@ -111,6 +117,14 @@ struct MetalContext {
|
||||
tsl::robin_set<MetalSamplerGroup*> samplerGroups;
|
||||
tsl::robin_set<MetalTexture*> textures;
|
||||
|
||||
// This circular buffer implements delayed destruction for Metal texture handles. It keeps a
|
||||
// handle to a fixed number of the most recently destroyed texture handles. When we're asked to
|
||||
// destroy a texture handle, we free its texture memory, but keep the MetalTexture object alive,
|
||||
// marking it as "terminated". If we later are asked to use that texture, we can check its
|
||||
// terminated status and throw an Objective-C error instead of crashing, which is helpful for
|
||||
// debugging use-after-free issues in release builds.
|
||||
utils::FixedCircularBuffer<Handle<HwTexture>> texturesToDestroy;
|
||||
|
||||
MetalBufferPool* bufferPool;
|
||||
|
||||
MetalSwapChain* currentDrawSwapChain = nil;
|
||||
@@ -136,6 +150,8 @@ struct MetalContext {
|
||||
|
||||
MTLViewport currentViewport;
|
||||
|
||||
MetalShaderCompiler* shaderCompiler = nullptr;
|
||||
|
||||
#if defined(FILAMENT_METAL_PROFILING)
|
||||
// Logging and profiling.
|
||||
os_log_t log;
|
||||
|
||||
@@ -34,11 +34,11 @@ namespace backend {
|
||||
class MetalPlatform;
|
||||
|
||||
class MetalBuffer;
|
||||
class MetalProgram;
|
||||
class MetalSamplerGroup;
|
||||
class MetalTexture;
|
||||
struct MetalUniformBuffer;
|
||||
struct MetalContext;
|
||||
struct MetalProgram;
|
||||
struct BufferState;
|
||||
|
||||
#ifndef FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB
|
||||
|
||||
@@ -50,7 +50,8 @@ UTILS_NOINLINE
|
||||
Driver* MetalDriver::create(MetalPlatform* const platform, const Platform::DriverConfig& driverConfig) {
|
||||
assert_invariant(platform);
|
||||
size_t defaultSize = FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig { .handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize) };
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||
return new MetalDriver(platform, validConfig);
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ Dispatcher MetalDriver::getDispatcher() const noexcept {
|
||||
|
||||
MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(*platform),
|
||||
mContext(new MetalContext),
|
||||
mContext(new MetalContext(driverConfig.textureUseAfterFreePoolSize)),
|
||||
mHandleAllocator("Handles", driverConfig.handleArenaSize) {
|
||||
mContext->driver = this;
|
||||
|
||||
@@ -143,6 +144,9 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
mContext->eventListener = [[MTLSharedEventListener alloc] initWithDispatchQueue:queue];
|
||||
}
|
||||
|
||||
mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this);
|
||||
mContext->shaderCompiler->init();
|
||||
|
||||
#if defined(FILAMENT_METAL_PROFILING)
|
||||
mContext->log = os_log_create("com.google.filament", "Metal");
|
||||
mContext->signpostId = os_signpost_id_generate(mContext->log);
|
||||
@@ -157,6 +161,7 @@ MetalDriver::~MetalDriver() noexcept {
|
||||
delete mContext->bufferPool;
|
||||
delete mContext->blitter;
|
||||
delete mContext->timerQueryImpl;
|
||||
delete mContext->shaderCompiler;
|
||||
delete mContext;
|
||||
}
|
||||
|
||||
@@ -303,8 +308,9 @@ void MetalDriver::importTextureR(Handle<HwTexture> th, intptr_t i,
|
||||
target, levels, format, samples, width, height, depth, usage, metalTexture));
|
||||
}
|
||||
|
||||
void MetalDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t size) {
|
||||
mContext->samplerGroups.insert(construct_handle<MetalSamplerGroup>(sbh, size));
|
||||
void MetalDriver::createSamplerGroupR(
|
||||
Handle<HwSamplerGroup> sbh, uint32_t size, utils::FixedSizeString<32> debugName) {
|
||||
mContext->samplerGroups.insert(construct_handle<MetalSamplerGroup>(sbh, size, debugName));
|
||||
}
|
||||
|
||||
void MetalDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
@@ -317,7 +323,7 @@ void MetalDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
}
|
||||
|
||||
void MetalDriver::createProgramR(Handle<HwProgram> rph, Program&& program) {
|
||||
construct_handle<MetalProgram>(rph, mContext->device, program);
|
||||
construct_handle<MetalProgram>(rph, *mContext, std::move(program));
|
||||
}
|
||||
|
||||
void MetalDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int dummy) {
|
||||
@@ -530,8 +536,18 @@ void MetalDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
return;
|
||||
}
|
||||
|
||||
mContext->textures.erase(handle_cast<MetalTexture>(th));
|
||||
destruct_handle<MetalTexture>(th);
|
||||
auto* metalTexture = handle_cast<MetalTexture>(th);
|
||||
mContext->textures.erase(metalTexture);
|
||||
|
||||
// Free memory from the texture and mark it as freed.
|
||||
metalTexture->terminate();
|
||||
|
||||
// Add this texture handle to our texturesToDestroy queue to be destroyed later.
|
||||
if (auto handleToFree = mContext->texturesToDestroy.push(th)) {
|
||||
// If texturesToDestroy is full, then .push evicts the oldest texture handle in the
|
||||
// queue (or simply th, if use-after-free detection is disabled).
|
||||
destruct_handle<MetalTexture>(handleToFree.value());
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
@@ -557,6 +573,12 @@ void MetalDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void MetalDriver::terminate() {
|
||||
// Terminate any outstanding MetalTextures.
|
||||
while (!mContext->texturesToDestroy.empty()) {
|
||||
Handle<HwTexture> toDestroy = mContext->texturesToDestroy.pop();
|
||||
destruct_handle<MetalTexture>(toDestroy);
|
||||
}
|
||||
|
||||
// finish() will flush the pending command buffer and will ensure all GPU work has finished.
|
||||
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
|
||||
finish();
|
||||
@@ -566,6 +588,7 @@ void MetalDriver::terminate() {
|
||||
|
||||
MetalExternalImage::shutdown(*mContext);
|
||||
mContext->blitter->shutdown();
|
||||
mContext->shaderCompiler->terminate();
|
||||
}
|
||||
|
||||
ShaderModel MetalDriver::getShaderModel() const noexcept {
|
||||
@@ -701,7 +724,7 @@ bool MetalDriver::isStereoSupported() {
|
||||
}
|
||||
|
||||
bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
@@ -854,13 +877,17 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
|
||||
assert_invariant(sb->size == data.size / sizeof(SamplerDescriptor));
|
||||
auto const* const samplers = (SamplerDescriptor const*) data.buffer;
|
||||
|
||||
#ifndef NDEBUG
|
||||
// In debug builds, verify that all the textures in the sampler group are still alive.
|
||||
// Verify that all the textures in the sampler group are still alive.
|
||||
// These bugs lead to memory corruption and can be difficult to track down.
|
||||
for (size_t s = 0; s < data.size / sizeof(SamplerDescriptor); s++) {
|
||||
if (!samplers[s].t) {
|
||||
continue;
|
||||
}
|
||||
// The difference between this check and the one below is that in release, we do this for
|
||||
// only a set number of recently freed textures, while the debug check is exhaustive.
|
||||
auto* metalTexture = handle_cast<MetalTexture>(samplers[s].t);
|
||||
metalTexture->checkUseAfterFree(sb->debugName.c_str(), s);
|
||||
#ifndef NDEBUG
|
||||
auto iter = mContext->textures.find(handle_cast<MetalTexture>(samplers[s].t));
|
||||
if (iter == mContext->textures.end()) {
|
||||
utils::slog.e << "updateSamplerGroup: texture #"
|
||||
@@ -868,8 +895,8 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
|
||||
<< samplers[s].t << utils::io::endl;
|
||||
}
|
||||
assert_invariant(iter != mContext->textures.end());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Create a MTLArgumentEncoder for these textures.
|
||||
// Ideally, we would create this encoder at createSamplerGroup time, but we need to know the
|
||||
@@ -935,7 +962,7 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
|
||||
void MetalDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
if (callback) {
|
||||
scheduleCallback(handler, user, callback);
|
||||
mContext->shaderCompiler->notifyWhenAllProgramsAreReady(handler, callback, user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1357,23 +1384,27 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(mContext);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// In debug builds, verify that all the textures in the sampler group are still alive.
|
||||
// Verify that all the textures in the sampler group are still alive.
|
||||
// These bugs lead to memory corruption and can be difficult to track down.
|
||||
const auto& handles = samplerGroup->getTextureHandles();
|
||||
for (size_t s = 0; s < handles.size(); s++) {
|
||||
if (!handles[s]) {
|
||||
continue;
|
||||
}
|
||||
auto iter = mContext->textures.find(handle_cast<MetalTexture>(handles[s]));
|
||||
// The difference between this check and the one below is that in release, we do this for
|
||||
// only a set number of recently freed textures, while the debug check is exhaustive.
|
||||
auto* metalTexture = handle_cast<MetalTexture>(handles[s]);
|
||||
metalTexture->checkUseAfterFree(samplerGroup->debugName.c_str(), s);
|
||||
#ifndef NDEBUG
|
||||
auto iter = mContext->textures.find(metalTexture);
|
||||
if (iter == mContext->textures.end()) {
|
||||
utils::slog.e << "finalizeSamplerGroup: texture #"
|
||||
<< (int) s << " is dead, texture handle = "
|
||||
<< handles[s] << utils::io::endl;
|
||||
}
|
||||
assert_invariant(iter != mContext->textures.end());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<id<MTLTexture>> newTextures(samplerGroup->size, nil);
|
||||
for (size_t binding = 0; binding < samplerGroup->size; binding++) {
|
||||
@@ -1447,14 +1478,19 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
auto program = handle_cast<MetalProgram>(ps.program);
|
||||
const auto& rs = ps.rasterState;
|
||||
|
||||
// This might block until the shader compilation has finished.
|
||||
auto functions = program->getFunctions();
|
||||
|
||||
// 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(!program->isValid)) {
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!functions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_PRECONDITION(program->isValid, "Attempting to draw with an invalid Metal program.");
|
||||
ASSERT_PRECONDITION(bool(functions), "Attempting to draw with an invalid Metal program.");
|
||||
|
||||
auto [fragment, vertex] = functions.getRasterFunctions();
|
||||
|
||||
// Pipeline state
|
||||
MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid };
|
||||
@@ -1477,8 +1513,8 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
|
||||
}
|
||||
MetalPipelineState pipelineState {
|
||||
.vertexFunction = program->vertexFunction,
|
||||
.fragmentFunction = program->fragmentFunction,
|
||||
.vertexFunction = vertex,
|
||||
.fragmentFunction = fragment,
|
||||
.vertexDescription = primitive->vertexDescription,
|
||||
.colorAttachmentPixelFormat = {
|
||||
colorPixelFormat[0],
|
||||
@@ -1623,7 +1659,7 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
if (!samplerGroup) {
|
||||
continue;
|
||||
}
|
||||
const auto& stageFlags = program->samplerGroupInfo[s].stageFlags;
|
||||
const auto& stageFlags = program->getSamplerGroupInfo()[s].stageFlags;
|
||||
if (stageFlags == ShaderStageFlags::NONE) {
|
||||
continue;
|
||||
}
|
||||
@@ -1650,26 +1686,23 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
|
||||
// Bind the user vertex buffers.
|
||||
|
||||
MetalBuffer* buffers[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
size_t bufferIndex = 0;
|
||||
size_t maxBufferIndex = 0;
|
||||
|
||||
auto vb = primitive->vertexBuffer;
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < vb->attributes.size(); attributeIndex++) {
|
||||
const auto& attribute = vb->attributes[attributeIndex];
|
||||
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_invariant(vb->buffers[attribute.buffer]);
|
||||
buffers[bufferIndex] = vb->buffers[attribute.buffer];
|
||||
vertexBufferOffsets[bufferIndex] = attribute.offset;
|
||||
bufferIndex++;
|
||||
for (auto m : primitive->bufferMapping) {
|
||||
assert_invariant(
|
||||
m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START &&
|
||||
m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT);
|
||||
size_t vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
|
||||
vertexBuffers[vertexBufferIndex] = vb->buffers[m.sourceBufferIndex];
|
||||
maxBufferIndex = std::max(maxBufferIndex, vertexBufferIndex);
|
||||
}
|
||||
|
||||
const auto bufferCount = bufferIndex;
|
||||
const auto bufferCount = maxBufferIndex + 1;
|
||||
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
|
||||
USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, buffers,
|
||||
USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, vertexBuffers,
|
||||
vertexBufferOffsets, bufferCount);
|
||||
|
||||
// Bind the zero buffer, used for missing vertex attributes.
|
||||
@@ -1696,21 +1729,26 @@ void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGro
|
||||
|
||||
auto mtlProgram = handle_cast<MetalProgram>(program);
|
||||
|
||||
// This might block until the shader compilation has finished.
|
||||
auto functions = mtlProgram->getFunctions();
|
||||
|
||||
// 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(!mtlProgram->isValid)) {
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!functions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert_invariant(mtlProgram->isValid && mtlProgram->computeFunction);
|
||||
auto compute = functions.getComputeFunction();
|
||||
|
||||
assert_invariant(bool(functions) && compute);
|
||||
|
||||
id<MTLComputeCommandEncoder> computeEncoder =
|
||||
[getPendingCommandBuffer(mContext) computeCommandEncoder];
|
||||
|
||||
NSError* error = nil;
|
||||
id<MTLComputePipelineState> computePipelineState =
|
||||
[mContext->device newComputePipelineStateWithFunction:mtlProgram->computeFunction
|
||||
[mContext->device newComputePipelineStateWithFunction:compute
|
||||
error:&error];
|
||||
if (error) {
|
||||
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -66,6 +67,7 @@ public:
|
||||
id<MTLTexture> acquireDrawable();
|
||||
|
||||
id<MTLTexture> acquireDepthTexture();
|
||||
id<MTLTexture> acquireStencilTexture();
|
||||
|
||||
void releaseDrawable();
|
||||
|
||||
@@ -94,12 +96,16 @@ private:
|
||||
void scheduleFrameScheduledCallback();
|
||||
void scheduleFrameCompletedCallback();
|
||||
|
||||
static MTLPixelFormat decideDepthStencilFormat(uint64_t flags);
|
||||
void ensureDepthStencilTexture();
|
||||
|
||||
MetalContext& context;
|
||||
id<CAMetalDrawable> drawable = nil;
|
||||
id<MTLTexture> depthTexture = nil;
|
||||
id<MTLTexture> depthStencilTexture = nil;
|
||||
id<MTLTexture> headlessDrawable = nil;
|
||||
NSUInteger headlessWidth;
|
||||
NSUInteger headlessHeight;
|
||||
MTLPixelFormat depthStencilFormat = MTLPixelFormatInvalid;
|
||||
NSUInteger headlessWidth = 0;
|
||||
NSUInteger headlessHeight = 0;
|
||||
CAMetalLayer* layer = nullptr;
|
||||
MetalExternalImage externalImage;
|
||||
SwapChainType type;
|
||||
@@ -154,6 +160,7 @@ struct MetalIndexBuffer : public HwIndexBuffer {
|
||||
};
|
||||
|
||||
struct MetalRenderPrimitive : public HwRenderPrimitive {
|
||||
MetalRenderPrimitive();
|
||||
void setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer);
|
||||
// The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak".
|
||||
// The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive.
|
||||
@@ -163,18 +170,35 @@ struct MetalRenderPrimitive : public HwRenderPrimitive {
|
||||
|
||||
// This struct is used to create the pipeline description to describe vertex assembly.
|
||||
VertexDescription vertexDescription = {};
|
||||
|
||||
struct Entry {
|
||||
uint8_t sourceBufferIndex = 0;
|
||||
uint8_t stride = 0;
|
||||
// maps to ->
|
||||
uint8_t bufferArgumentIndex = 0;
|
||||
|
||||
Entry(uint8_t sourceBufferIndex, uint8_t stride, uint8_t bufferArgumentIndex)
|
||||
: sourceBufferIndex(sourceBufferIndex),
|
||||
stride(stride),
|
||||
bufferArgumentIndex(bufferArgumentIndex) {}
|
||||
};
|
||||
utils::FixedCapacityVector<Entry> bufferMapping;
|
||||
};
|
||||
|
||||
struct MetalProgram : public HwProgram {
|
||||
MetalProgram(id<MTLDevice> device, const Program& program) noexcept;
|
||||
class MetalProgram : public HwProgram {
|
||||
public:
|
||||
MetalProgram(MetalContext& context, Program&& program) noexcept;
|
||||
|
||||
id<MTLFunction> vertexFunction;
|
||||
id<MTLFunction> fragmentFunction;
|
||||
id<MTLFunction> computeFunction;
|
||||
const MetalShaderCompiler::MetalFunctionBundle& getFunctions();
|
||||
const Program::SamplerGroupInfo& getSamplerGroupInfo() { return samplerGroupInfo; }
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
|
||||
bool isValid = false;
|
||||
MetalContext& mContext;
|
||||
MetalShaderCompiler::MetalFunctionBundle mFunctionBundle;
|
||||
MetalShaderCompiler::program_token_t mToken;
|
||||
};
|
||||
|
||||
struct PixelBufferShape {
|
||||
@@ -223,8 +247,8 @@ public:
|
||||
// - using the texture as a render target attachment
|
||||
// - calling setMinMaxLevels
|
||||
// A texture's available mips are consistent throughout a render pass.
|
||||
void setLodRange(uint32_t minLevel, uint32_t maxLevel);
|
||||
void extendLodRangeTo(uint32_t level);
|
||||
void setLodRange(uint16_t minLevel, uint16_t maxLevel);
|
||||
void extendLodRangeTo(uint16_t level);
|
||||
|
||||
static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format);
|
||||
|
||||
@@ -238,6 +262,26 @@ public:
|
||||
|
||||
MTLPixelFormat devicePixelFormat;
|
||||
|
||||
// Frees memory associated with this texture and marks it as "terminated".
|
||||
// Used to track "use after free" scenario.
|
||||
void terminate() noexcept;
|
||||
bool isTerminated() const noexcept { return terminated; }
|
||||
inline void checkUseAfterFree(const char* samplerGroupDebugName, size_t textureIndex) const {
|
||||
if (UTILS_LIKELY(!isTerminated())) {
|
||||
return;
|
||||
}
|
||||
NSString* reason =
|
||||
[NSString stringWithFormat:
|
||||
@"Filament Metal texture use after free, sampler group = "
|
||||
@"%s, texture index = %zu",
|
||||
samplerGroupDebugName, textureIndex];
|
||||
NSException* useAfterFreeException =
|
||||
[NSException exceptionWithName:@"MetalTextureUseAfterFree"
|
||||
reason:reason
|
||||
userInfo:nil];
|
||||
[useAfterFreeException raise];
|
||||
}
|
||||
|
||||
private:
|
||||
void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
|
||||
PixelBufferDescriptor const& data) noexcept;
|
||||
@@ -254,14 +298,17 @@ private:
|
||||
id<MTLTexture> swizzledTextureView = nil;
|
||||
id<MTLTexture> lodTextureView = nil;
|
||||
|
||||
uint32_t minLod = UINT_MAX;
|
||||
uint32_t maxLod = 0;
|
||||
uint16_t minLod = std::numeric_limits<uint16_t>::max();
|
||||
uint16_t maxLod = 0;
|
||||
|
||||
bool terminated = false;
|
||||
};
|
||||
|
||||
class MetalSamplerGroup : public HwSamplerGroup {
|
||||
public:
|
||||
explicit MetalSamplerGroup(size_t size) noexcept
|
||||
explicit MetalSamplerGroup(size_t size, utils::FixedSizeString<32> name) noexcept
|
||||
: size(size),
|
||||
debugName(name),
|
||||
textureHandles(size, Handle<HwTexture>()),
|
||||
textures(size, nil),
|
||||
samplers(size, nil) {}
|
||||
@@ -271,12 +318,10 @@ public:
|
||||
textureHandles[index] = th;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// This method is only used for debugging, to ensure all texture handles are alive.
|
||||
const auto& getTextureHandles() const {
|
||||
return textureHandles;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Encode a MTLTexture into this SamplerGroup at the given index.
|
||||
inline void setFinalizedTexture(size_t index, id<MTLTexture> t) {
|
||||
@@ -322,6 +367,7 @@ public:
|
||||
void useResources(id<MTLRenderCommandEncoder> renderPassEncoder);
|
||||
|
||||
size_t size;
|
||||
utils::FixedSizeString<32> debugName;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -57,8 +57,11 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
}
|
||||
|
||||
MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow, uint64_t flags)
|
||||
: context(context), layer(nativeWindow), externalImage(context),
|
||||
type(SwapChainType::CAMETALLAYER) {
|
||||
: context(context),
|
||||
depthStencilFormat(decideDepthStencilFormat(flags)),
|
||||
layer(nativeWindow),
|
||||
externalImage(context),
|
||||
type(SwapChainType::CAMETALLAYER) {
|
||||
|
||||
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
|
||||
utils::slog.w << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, "
|
||||
@@ -79,17 +82,30 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow
|
||||
}
|
||||
|
||||
MetalSwapChain::MetalSwapChain(MetalContext& context, int32_t width, int32_t height, uint64_t flags)
|
||||
: context(context), headlessWidth(width), headlessHeight(height), externalImage(context),
|
||||
type(SwapChainType::HEADLESS) { }
|
||||
: context(context),
|
||||
depthStencilFormat(decideDepthStencilFormat(flags)),
|
||||
headlessWidth(width),
|
||||
headlessHeight(height),
|
||||
externalImage(context),
|
||||
type(SwapChainType::HEADLESS) {}
|
||||
|
||||
MetalSwapChain::MetalSwapChain(MetalContext& context, CVPixelBufferRef pixelBuffer, uint64_t flags)
|
||||
: context(context), externalImage(context), type(SwapChainType::CVPIXELBUFFERREF) {
|
||||
: context(context),
|
||||
depthStencilFormat(decideDepthStencilFormat(flags)),
|
||||
externalImage(context),
|
||||
type(SwapChainType::CVPIXELBUFFERREF) {
|
||||
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
|
||||
MetalExternalImage::assertWritableImage(pixelBuffer);
|
||||
externalImage.set(pixelBuffer);
|
||||
assert_invariant(externalImage.isValid());
|
||||
}
|
||||
|
||||
MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
|
||||
// These formats are supported on all devices, both iOS and macOS.
|
||||
return flags & SwapChain::CONFIG_HAS_STENCIL_BUFFER ? MTLPixelFormatDepth32Float_Stencil8
|
||||
: MTLPixelFormatDepth32Float;
|
||||
}
|
||||
|
||||
MetalSwapChain::~MetalSwapChain() {
|
||||
externalImage.set(nullptr);
|
||||
}
|
||||
@@ -156,37 +172,40 @@ void MetalSwapChain::releaseDrawable() {
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
|
||||
if (depthTexture) {
|
||||
// If the surface size has changed, we'll need to allocate a new depth texture.
|
||||
if (depthTexture.width != getSurfaceWidth() ||
|
||||
depthTexture.height != getSurfaceHeight()) {
|
||||
depthTexture = nil;
|
||||
ensureDepthStencilTexture();
|
||||
assert_invariant(depthStencilTexture);
|
||||
return depthStencilTexture;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireStencilTexture() {
|
||||
if (!isMetalFormatStencil(depthStencilFormat)) {
|
||||
return nil;
|
||||
}
|
||||
ensureDepthStencilTexture();
|
||||
assert_invariant(depthStencilTexture);
|
||||
return depthStencilTexture;
|
||||
}
|
||||
|
||||
void MetalSwapChain::ensureDepthStencilTexture() {
|
||||
NSUInteger width = getSurfaceWidth();
|
||||
NSUInteger height = getSurfaceHeight();
|
||||
if (UTILS_LIKELY(depthStencilTexture)) {
|
||||
// If the surface size has changed, we'll need to allocate a new depth/stencil texture.
|
||||
if (UTILS_UNLIKELY(
|
||||
depthStencilTexture.width != width || depthStencilTexture.height != height)) {
|
||||
depthStencilTexture = nil;
|
||||
} else {
|
||||
return depthTexture;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const MTLPixelFormat depthFormat =
|
||||
#if defined(IOS)
|
||||
MTLPixelFormatDepth32Float;
|
||||
#else
|
||||
context.device.depth24Stencil8PixelFormatSupported ?
|
||||
MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float;
|
||||
#endif
|
||||
|
||||
const NSUInteger width = getSurfaceWidth();
|
||||
const NSUInteger height = getSurfaceHeight();
|
||||
MTLTextureDescriptor* descriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthFormat
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthStencilFormat
|
||||
width:width
|
||||
height:height
|
||||
mipmapped:NO];
|
||||
descriptor.usage = MTLTextureUsageRenderTarget;
|
||||
descriptor.resourceOptions = MTLResourceStorageModePrivate;
|
||||
|
||||
depthTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
|
||||
return depthTexture;
|
||||
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) {
|
||||
@@ -280,6 +299,9 @@ MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uin
|
||||
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
|
||||
|
||||
MetalRenderPrimitive::MetalRenderPrimitive()
|
||||
: bufferMapping(utils::FixedCapacityVector<Entry>::with_capacity(MAX_VERTEX_BUFFER_COUNT)) {}
|
||||
|
||||
void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer*
|
||||
indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
@@ -287,118 +309,94 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalInde
|
||||
|
||||
const size_t attributeCount = vertexBuffer->attributes.size();
|
||||
|
||||
auto& mapping = bufferMapping;
|
||||
mapping.clear();
|
||||
vertexDescription = {};
|
||||
|
||||
// Each attribute gets its own vertex buffer, starting at logical buffer 1.
|
||||
uint32_t bufferIndex = 1;
|
||||
// Set the layout for the zero buffer, which unused attributes are mapped to.
|
||||
vertexDescription.layouts[ZERO_VERTEX_BUFFER_LOGICAL_INDEX] = {
|
||||
.step = MTLVertexStepFunctionConstant, .stride = 16
|
||||
};
|
||||
|
||||
// Here we map each source buffer to a Metal buffer argument.
|
||||
// Each attribute has a source buffer, offset, and stride.
|
||||
// Two source buffers with the same index and stride can share the same Metal buffer argument
|
||||
// index.
|
||||
//
|
||||
// The source buffer is the buffer index that the Filament client sets.
|
||||
// * source buffer
|
||||
// .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12)
|
||||
// .attribute(VertexAttribute::UV, 0, VertexBuffer::AttributeType::HALF2, 8, 12)
|
||||
// .attribute(VertexAttribute::COLOR, 1, VertexBuffer::AttributeType::UBYTE4, 0, 4)
|
||||
|
||||
auto allocateOrGetBufferArgumentIndex =
|
||||
[&mapping, currentBufferArgumentIndex = USER_VERTEX_BUFFER_BINDING_START, this](
|
||||
auto sourceBuffer, auto sourceBufferStride) mutable -> uint8_t {
|
||||
auto match = [&](const auto& e) {
|
||||
return e.sourceBufferIndex == sourceBuffer && e.stride == sourceBufferStride;
|
||||
};
|
||||
if (auto it = std::find_if(mapping.begin(), mapping.end(), match); it != mapping.end()) {
|
||||
return it->bufferArgumentIndex;
|
||||
} else {
|
||||
auto bufferArgumentIndex = currentBufferArgumentIndex++;
|
||||
mapping.emplace_back(sourceBuffer, sourceBufferStride, bufferArgumentIndex);
|
||||
vertexDescription.layouts[bufferArgumentIndex] = {
|
||||
.step = MTLVertexStepFunctionPerVertex, .stride = sourceBufferStride
|
||||
};
|
||||
return bufferArgumentIndex;
|
||||
}
|
||||
};
|
||||
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
|
||||
const auto& attribute = vertexBuffer->attributes[attributeIndex];
|
||||
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
|
||||
const uint8_t flags = attribute.flags;
|
||||
const MTLVertexFormat format = (flags & Attribute::FLAG_INTEGER_TARGET) ?
|
||||
MTLVertexFormatUInt4 : MTLVertexFormatFloat4;
|
||||
|
||||
// If the attribute is not enabled, bind it to the zero buffer. It's a Metal error for a
|
||||
// shader to read from missing vertex attributes.
|
||||
// If the attribute is unused, bind it to the zero buffer. It's a Metal error for a shader
|
||||
// to read from missing vertex attributes.
|
||||
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
|
||||
const MTLVertexFormat format = (attribute.flags & Attribute::FLAG_INTEGER_TARGET)
|
||||
? MTLVertexFormatUInt4
|
||||
: MTLVertexFormatFloat4;
|
||||
vertexDescription.attributes[attributeIndex] = {
|
||||
.format = format,
|
||||
.buffer = ZERO_VERTEX_BUFFER_LOGICAL_INDEX,
|
||||
.offset = 0
|
||||
};
|
||||
vertexDescription.layouts[ZERO_VERTEX_BUFFER_LOGICAL_INDEX] = {
|
||||
.step = MTLVertexStepFunctionConstant,
|
||||
.stride = 16
|
||||
.format = format, .buffer = ZERO_VERTEX_BUFFER_LOGICAL_INDEX, .offset = 0
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map the source buffer and stride of this attribute to a Metal buffer argument.
|
||||
auto bufferArgumentIndex =
|
||||
allocateOrGetBufferArgumentIndex(attribute.buffer, attribute.stride);
|
||||
|
||||
vertexDescription.attributes[attributeIndex] = {
|
||||
.format = getMetalFormat(attribute.type,
|
||||
attribute.flags & Attribute::FLAG_NORMALIZED),
|
||||
.buffer = bufferIndex,
|
||||
.offset = 0
|
||||
.format = getMetalFormat(
|
||||
attribute.type, attribute.flags & Attribute::FLAG_NORMALIZED),
|
||||
.buffer = uint32_t(bufferArgumentIndex),
|
||||
.offset = attribute.offset
|
||||
};
|
||||
vertexDescription.layouts[bufferIndex] = {
|
||||
.step = MTLVertexStepFunctionPerVertex,
|
||||
.stride = attribute.stride
|
||||
};
|
||||
|
||||
bufferIndex++;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
MetalProgram::MetalProgram(id<MTLDevice> device, const Program& program) noexcept
|
||||
: HwProgram(program.getName()), vertexFunction(nil), fragmentFunction(nil),
|
||||
computeFunction(nil), isValid(false) {
|
||||
|
||||
using MetalFunctionPtr = __strong id<MTLFunction>*;
|
||||
|
||||
static_assert(Program::SHADER_TYPE_COUNT == 3, "Only vertex, fragment, and/or compute shaders expected.");
|
||||
MetalFunctionPtr shaderFunctions[3] = { &vertexFunction, &fragmentFunction, &computeFunction };
|
||||
|
||||
const auto& sources = program.getShadersSource();
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const auto& source = sources[i];
|
||||
// It's okay for some shaders to be empty, they shouldn't be used in any draw calls.
|
||||
if (source.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_invariant( source[source.size() - 1] == '\0' );
|
||||
|
||||
// the shader string is null terminated and the length includes the null character
|
||||
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
|
||||
length:source.size() - 1
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
// When options is nil, Metal uses the most recent language version available.
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
if (library == nil) {
|
||||
if (error) {
|
||||
auto description =
|
||||
[error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
utils::slog.w << description << utils::io::endl;
|
||||
}
|
||||
PANIC_LOG("Failed to compile Metal program.");
|
||||
return;
|
||||
}
|
||||
|
||||
MTLFunctionConstantValues* constants = [MTLFunctionConstantValues new];
|
||||
auto const& specializationConstants = program.getSpecializationConstants();
|
||||
for (auto const& sc : specializationConstants) {
|
||||
const std::array<MTLDataType, 3> types{
|
||||
MTLDataTypeInt, MTLDataTypeFloat, MTLDataTypeBool };
|
||||
std::visit([&sc, constants, type = types[sc.value.index()]](auto&& arg) {
|
||||
[constants setConstantValue:&arg
|
||||
type:type
|
||||
atIndex:sc.id];
|
||||
}, sc.value);
|
||||
}
|
||||
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"main0"
|
||||
constantValues:constants
|
||||
error:&error];
|
||||
if (!program.getName().empty()) {
|
||||
function.label = @(program.getName().c_str());
|
||||
}
|
||||
assert_invariant(function);
|
||||
*shaderFunctions[i] = function;
|
||||
}
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE const bool isRasterizationProgram =
|
||||
vertexFunction != nil && fragmentFunction != nil;
|
||||
UTILS_UNUSED_IN_RELEASE const bool isComputeProgram = computeFunction != nil;
|
||||
// The program must be either a rasterization program XOR a compute program.
|
||||
assert_invariant(isRasterizationProgram != isComputeProgram);
|
||||
|
||||
// All stages of the program have compiled successfully, this is a valid program.
|
||||
isValid = true;
|
||||
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
|
||||
: HwProgram(program.getName()), mContext(context) {
|
||||
|
||||
// Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to
|
||||
// the appropriate stage(s).
|
||||
samplerGroupInfo = program.getSamplerGroupInfo();
|
||||
|
||||
mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program));
|
||||
assert_invariant(mToken);
|
||||
}
|
||||
|
||||
const MetalShaderCompiler::MetalFunctionBundle& MetalProgram::getFunctions() {
|
||||
initialize();
|
||||
return mFunctionBundle;
|
||||
}
|
||||
|
||||
void MetalProgram::initialize() {
|
||||
if (!mToken) {
|
||||
return;
|
||||
}
|
||||
mFunctionBundle = mContext.shaderCompiler->getProgram(mToken);
|
||||
assert_invariant(!mToken);
|
||||
}
|
||||
|
||||
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels,
|
||||
@@ -516,6 +514,15 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
setLodRange(0, levels - 1);
|
||||
}
|
||||
|
||||
void MetalTexture::terminate() noexcept {
|
||||
texture = nil;
|
||||
swizzledTextureView = nil;
|
||||
lodTextureView = nil;
|
||||
msaaSidecar = nil;
|
||||
externalImage.set(nullptr);
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
MetalTexture::~MetalTexture() {
|
||||
externalImage.set(nullptr);
|
||||
}
|
||||
@@ -788,14 +795,14 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit");
|
||||
}
|
||||
|
||||
void MetalTexture::extendLodRangeTo(uint32_t level) {
|
||||
void MetalTexture::extendLodRangeTo(uint16_t level) {
|
||||
assert_invariant(!isInRenderPass(&context));
|
||||
minLod = std::min(minLod, level);
|
||||
maxLod = std::max(maxLod, level);
|
||||
lodTextureView = nil;
|
||||
}
|
||||
|
||||
void MetalTexture::setLodRange(uint32_t min, uint32_t max) {
|
||||
void MetalTexture::setLodRange(uint16_t min, uint16_t max) {
|
||||
assert_invariant(!isInRenderPass(&context));
|
||||
assert_invariant(min <= max);
|
||||
minLod = min;
|
||||
@@ -1123,7 +1130,7 @@ MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() {
|
||||
MetalRenderTarget::Attachment MetalRenderTarget::getStencilAttachment() {
|
||||
Attachment result = stencil;
|
||||
if (defaultRenderTarget) {
|
||||
// TODO: do we want the default SwapChain to have a default stencil buffer?
|
||||
result.texture = context->currentDrawSwapChain->acquireStencilTexture();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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_METAL_METALSHADERCOMPILER_H
|
||||
#define TNT_FILAMENT_BACKEND_METAL_METALSHADERCOMPILER_H
|
||||
|
||||
#include "CompilerThreadPool.h"
|
||||
|
||||
#include "CallbackManager.h"
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class MetalDriver;
|
||||
|
||||
class MetalShaderCompiler {
|
||||
struct MetalProgramToken;
|
||||
|
||||
public:
|
||||
class MetalFunctionBundle {
|
||||
public:
|
||||
MetalFunctionBundle() = default;
|
||||
MetalFunctionBundle(id<MTLFunction> fragment, id<MTLFunction> vertex)
|
||||
: functions{fragment, vertex} {
|
||||
assert_invariant(fragment && vertex);
|
||||
assert_invariant(fragment.functionType == MTLFunctionTypeFragment);
|
||||
assert_invariant(vertex.functionType == MTLFunctionTypeVertex);
|
||||
}
|
||||
explicit MetalFunctionBundle(id<MTLFunction> compute) : functions{compute, nil} {
|
||||
assert_invariant(compute);
|
||||
assert_invariant(compute.functionType == MTLFunctionTypeKernel);
|
||||
}
|
||||
|
||||
std::pair<id<MTLFunction>, id<MTLFunction>> getRasterFunctions() const noexcept {
|
||||
assert_invariant(functions[0].functionType == MTLFunctionTypeFragment);
|
||||
assert_invariant(functions[1].functionType == MTLFunctionTypeVertex);
|
||||
return {functions[0], functions[1]};
|
||||
}
|
||||
|
||||
id<MTLFunction> getComputeFunction() const noexcept {
|
||||
assert_invariant(functions[0].functionType == MTLFunctionTypeKernel);
|
||||
return functions[0];
|
||||
}
|
||||
|
||||
explicit operator bool() const { return functions[0] != nil; }
|
||||
|
||||
private:
|
||||
// Can hold two functions, either:
|
||||
// - fragment and vertex (for rasterization pipelines)
|
||||
// - compute (for compute pipelines)
|
||||
id<MTLFunction> functions[2] = {nil, nil};
|
||||
};
|
||||
|
||||
using program_token_t = std::shared_ptr<MetalProgramToken>;
|
||||
|
||||
explicit MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver);
|
||||
|
||||
MetalShaderCompiler(MetalShaderCompiler const& rhs) = delete;
|
||||
MetalShaderCompiler(MetalShaderCompiler&& rhs) = delete;
|
||||
MetalShaderCompiler& operator=(MetalShaderCompiler const& rhs) = delete;
|
||||
MetalShaderCompiler& operator=(MetalShaderCompiler&& rhs) = delete;
|
||||
|
||||
void init() noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
// Creates a program asynchronously
|
||||
program_token_t createProgram(utils::CString const& name, Program&& program);
|
||||
|
||||
// Returns the functions, blocking if necessary. The Token is destroyed and becomes invalid.
|
||||
MetalFunctionBundle getProgram(program_token_t& token);
|
||||
|
||||
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
|
||||
static void terminate(program_token_t& token);
|
||||
|
||||
void notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
|
||||
private:
|
||||
static MetalFunctionBundle compileProgram(const Program& program, id<MTLDevice> device);
|
||||
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
id<MTLDevice> mDevice;
|
||||
CallbackManager mCallbackManager;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_METAL_METALSHADERCOMPILER_H
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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 "MetalShaderCompiler.h"
|
||||
|
||||
#include "MetalDriver.h"
|
||||
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace utils;
|
||||
|
||||
struct MetalShaderCompiler::MetalProgramToken : ProgramToken {
|
||||
|
||||
MetalProgramToken(MetalShaderCompiler& compiler) noexcept
|
||||
: compiler(compiler) {
|
||||
}
|
||||
~MetalProgramToken() override;
|
||||
|
||||
void set(MetalFunctionBundle p) noexcept {
|
||||
std::unique_lock const l(lock);
|
||||
std::swap(program, p);
|
||||
signaled = true;
|
||||
cond.notify_one();
|
||||
}
|
||||
|
||||
MetalFunctionBundle get() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
cond.wait(l, [this](){ return signaled; });
|
||||
return program;
|
||||
}
|
||||
|
||||
void wait() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
cond.wait(l, [this]() { return signaled; });
|
||||
}
|
||||
|
||||
bool isReady() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
using namespace std::chrono_literals;
|
||||
return cond.wait_for(l, 0s, [this]() { return signaled; });
|
||||
}
|
||||
|
||||
MetalShaderCompiler& compiler;
|
||||
CallbackManager::Handle handle{};
|
||||
MetalFunctionBundle program{};
|
||||
mutable utils::Mutex lock;
|
||||
mutable utils::Condition cond;
|
||||
bool signaled = false;
|
||||
};
|
||||
|
||||
MetalShaderCompiler::MetalProgramToken::~MetalProgramToken() = default;
|
||||
|
||||
MetalShaderCompiler::MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver)
|
||||
: mDevice(device),
|
||||
mCallbackManager(driver) {
|
||||
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::init() noexcept {
|
||||
const uint32_t poolSize = 2;
|
||||
mCompilerThreadPool.init(poolSize, []() {}, []() {});
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::terminate() noexcept {
|
||||
mCompilerThreadPool.terminate();
|
||||
mCallbackManager.terminate();
|
||||
}
|
||||
|
||||
/* static */ MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::compileProgram(
|
||||
const Program& program, id<MTLDevice> device) {
|
||||
std::array<id<MTLFunction>, Program::SHADER_TYPE_COUNT> functions = { nil };
|
||||
const auto& sources = program.getShadersSource();
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const auto& source = sources[i];
|
||||
// It's okay for some shaders to be empty, they shouldn't be used in any draw calls.
|
||||
if (source.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_invariant(source[source.size() - 1] == '\0');
|
||||
|
||||
// the shader string is null terminated and the length includes the null character
|
||||
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
|
||||
length:source.size() - 1
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
// When options is nil, Metal uses the most recent language version available.
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
if (library == nil) {
|
||||
if (error) {
|
||||
auto description =
|
||||
[error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
utils::slog.w << description << utils::io::endl;
|
||||
}
|
||||
PANIC_LOG("Failed to compile Metal program.");
|
||||
return {};
|
||||
}
|
||||
|
||||
MTLFunctionConstantValues* constants = [MTLFunctionConstantValues new];
|
||||
auto const& specializationConstants = program.getSpecializationConstants();
|
||||
for (auto const& sc : specializationConstants) {
|
||||
const std::array<MTLDataType, 3> types{
|
||||
MTLDataTypeInt, MTLDataTypeFloat, MTLDataTypeBool };
|
||||
std::visit([&sc, constants, type = types[sc.value.index()]](auto&& arg) {
|
||||
[constants setConstantValue:&arg
|
||||
type:type
|
||||
atIndex:sc.id];
|
||||
}, sc.value);
|
||||
}
|
||||
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"main0"
|
||||
constantValues:constants
|
||||
error:&error];
|
||||
if (!program.getName().empty()) {
|
||||
function.label = @(program.getName().c_str());
|
||||
}
|
||||
assert_invariant(function);
|
||||
functions[i] = function;
|
||||
}
|
||||
|
||||
static_assert(Program::SHADER_TYPE_COUNT == 3,
|
||||
"Only vertex, fragment, and/or compute shaders expected.");
|
||||
id<MTLFunction> vertexFunction = functions[0];
|
||||
id<MTLFunction> fragmentFunction = functions[1];
|
||||
id<MTLFunction> computeFunction = functions[2];
|
||||
const bool isRasterizationProgram = vertexFunction != nil && fragmentFunction != nil;
|
||||
const bool isComputeProgram = computeFunction != nil;
|
||||
// The program must be either a rasterization program XOR a compute program.
|
||||
assert_invariant(isRasterizationProgram != isComputeProgram);
|
||||
|
||||
if (isRasterizationProgram) {
|
||||
return {fragmentFunction, vertexFunction};
|
||||
}
|
||||
|
||||
if (isComputeProgram) {
|
||||
return MetalFunctionBundle{computeFunction};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram(
|
||||
CString const& name, Program&& program) {
|
||||
auto token = std::make_shared<MetalProgramToken>(*this);
|
||||
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, name, device = mDevice, program = std::move(program), token]() {
|
||||
int sleepTime = atoi(name.c_str());
|
||||
sleep(sleepTime);
|
||||
|
||||
MetalFunctionBundle compiledProgram = compileProgram(program, device);
|
||||
|
||||
token->set(compiledProgram);
|
||||
mCallbackManager.put(token->handle);
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::getProgram(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
if (!token->isReady()) {
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
job();
|
||||
}
|
||||
}
|
||||
|
||||
MetalShaderCompiler::MetalFunctionBundle program = token->get();
|
||||
|
||||
token = nullptr;
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
/* static */ void MetalShaderCompiler::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
if (!job) {
|
||||
// The job is being executed right now (or has already executed).
|
||||
token->wait();
|
||||
} else {
|
||||
// The job has not executed yet.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
|
||||
token.reset();
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
mCallbackManager.setCallback(handler, callback, user);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -373,16 +373,19 @@ constexpr inline GLenum getCullingMode(CullingMode mode) noexcept {
|
||||
constexpr inline std::pair<GLenum, GLenum> textureFormatToFormatAndType(
|
||||
TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::RGBA8: return { GL_RGBA, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::RGB565: return { GL_RGB, GL_UNSIGNED_SHORT_5_6_5 };
|
||||
case TextureFormat::RGB5_A1: return { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 };
|
||||
case TextureFormat::RGBA4: return { GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 };
|
||||
case TextureFormat::DEPTH16: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT };
|
||||
case TextureFormat::DEPTH24: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_INT };
|
||||
case TextureFormat::R8: return { 0x1909 /*GL_LUMINANCE*/, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::SRGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::RGBA8: return { GL_RGBA, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::SRGB8_A8: return { GL_RGBA, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::RGB565: return { GL_RGB, GL_UNSIGNED_SHORT_5_6_5 };
|
||||
case TextureFormat::RGB5_A1: return { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 };
|
||||
case TextureFormat::RGBA4: return { GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 };
|
||||
case TextureFormat::DEPTH16: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT };
|
||||
case TextureFormat::DEPTH24: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_INT };
|
||||
case TextureFormat::DEPTH24_STENCIL8:
|
||||
return { GL_DEPTH24_STENCIL8, GL_UNSIGNED_INT_24_8 };
|
||||
default: return { GL_NONE, GL_NONE };
|
||||
return { GL_DEPTH24_STENCIL8, GL_UNSIGNED_INT_24_8 };
|
||||
default: return { GL_NONE, GL_NONE };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include "OpenGLBlobCache.h"
|
||||
|
||||
#include "OpenGLContext.h"
|
||||
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
@@ -28,17 +30,18 @@ struct OpenGLBlobCache::Blob {
|
||||
char data[];
|
||||
};
|
||||
|
||||
GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
Program const& program) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
OpenGLBlobCache::OpenGLBlobCache(OpenGLContext& gl) noexcept
|
||||
: mCachingSupported(gl.gets.num_program_binary_formats >= 1) {
|
||||
}
|
||||
|
||||
if (!platform.hasBlobFunc()) {
|
||||
GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
Program const& program) const noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (!mCachingSupported || !platform.hasRetrieveBlobFunc()) {
|
||||
// the key is never updated in that case
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYSTRACE_CONTEXT();
|
||||
|
||||
GLuint programId = 0;
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
@@ -64,8 +67,10 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
|
||||
programId = glCreateProgram();
|
||||
|
||||
SYSTRACE_NAME("glProgramBinary");
|
||||
glProgramBinary(programId, blob->format, blob->data, programBinarySize);
|
||||
{ // scope for systrace
|
||||
SYSTRACE_NAME("glProgramBinary");
|
||||
glProgramBinary(programId, blob->format, blob->data, programBinarySize);
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(glGetError() != GL_NO_ERROR)) {
|
||||
// glProgramBinary can fail if for instance the driver has been updated
|
||||
@@ -85,19 +90,28 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
|
||||
void OpenGLBlobCache::insert(Platform& platform,
|
||||
BlobCacheKey const& key, GLuint program) noexcept {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
SYSTRACE_CALL();
|
||||
if (platform.hasBlobFunc()) {
|
||||
SYSTRACE_CONTEXT();
|
||||
GLenum format;
|
||||
GLint programBinarySize;
|
||||
if (!mCachingSupported || !platform.hasInsertBlobFunc()) {
|
||||
// the key is never updated in that case
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLenum format;
|
||||
GLint programBinarySize = 0;
|
||||
{ // scope for systrace
|
||||
SYSTRACE_NAME("glGetProgramiv");
|
||||
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programBinarySize);
|
||||
if (programBinarySize) {
|
||||
size_t const size = sizeof(Blob) + programBinarySize;
|
||||
std::unique_ptr<Blob, decltype(&::free)> blob{ (Blob*)malloc(size), &::free };
|
||||
SYSTRACE_NAME("glGetProgramBinary");
|
||||
glGetProgramBinary(program, programBinarySize, &programBinarySize, &format, blob->data);
|
||||
}
|
||||
if (programBinarySize) {
|
||||
size_t const size = sizeof(Blob) + programBinarySize;
|
||||
std::unique_ptr<Blob, decltype(&::free)> blob{ (Blob*)malloc(size), &::free };
|
||||
if (UTILS_LIKELY(blob)) {
|
||||
{ // scope for systrace
|
||||
SYSTRACE_NAME("glGetProgramBinary");
|
||||
glGetProgramBinary(program, programBinarySize,
|
||||
&programBinarySize, &format, blob->data);
|
||||
}
|
||||
GLenum const error = glGetError();
|
||||
if (error == GL_NO_ERROR) {
|
||||
blob->format = format;
|
||||
@@ -108,18 +122,4 @@ void OpenGLBlobCache::insert(Platform& platform,
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLBlobCache::insert(Platform& platform, BlobCacheKey const& key,
|
||||
GLenum format, void* data, GLsizei programBinarySize) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (platform.hasBlobFunc()) {
|
||||
if (programBinarySize) {
|
||||
size_t const size = sizeof(Blob) + programBinarySize;
|
||||
std::unique_ptr<Blob, decltype(&::free)> blob{ (Blob*)malloc(size), &::free };
|
||||
blob->format = format;
|
||||
memcpy(blob->data, data, programBinarySize);
|
||||
platform.insertBlob(key.data(), key.size(), blob.get(), size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -25,20 +25,21 @@ namespace filament::backend {
|
||||
|
||||
class Platform;
|
||||
class Program;
|
||||
class OpenGLContext;
|
||||
|
||||
class OpenGLBlobCache {
|
||||
public:
|
||||
static GLuint retrieve(BlobCacheKey* key, Platform& platform,
|
||||
Program const& program) noexcept;
|
||||
explicit OpenGLBlobCache(OpenGLContext& gl) noexcept;
|
||||
|
||||
static void insert(Platform& platform,
|
||||
GLuint retrieve(BlobCacheKey* key, Platform& platform,
|
||||
Program const& program) const noexcept;
|
||||
|
||||
void insert(Platform& platform,
|
||||
BlobCacheKey const& key, GLuint program) noexcept;
|
||||
|
||||
static void insert(Platform& platform, BlobCacheKey const& key,
|
||||
GLenum format, void* data, GLsizei programBinarySize) noexcept;
|
||||
|
||||
private:
|
||||
struct Blob;
|
||||
bool mCachingSupported = false;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -99,38 +99,41 @@ OpenGLContext::OpenGLContext() noexcept {
|
||||
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
|
||||
&gets.max_uniform_block_size);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
||||
&gets.max_uniform_buffer_bindings);
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,
|
||||
&gets.uniform_buffer_offset_alignment);
|
||||
glGetIntegerv(GL_MAX_SAMPLES,
|
||||
&gets.max_samples);
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS,
|
||||
&gets.max_draw_buffers);
|
||||
glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
|
||||
&gets.max_transform_feedback_separate_attribs);
|
||||
#ifdef GL_EXT_texture_filter_anisotropic
|
||||
if (ext.EXT_texture_filter_anisotropic) {
|
||||
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
|
||||
}
|
||||
#endif
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS,
|
||||
&gets.max_draw_buffers);
|
||||
glGetIntegerv(GL_MAX_SAMPLES,
|
||||
&gets.max_samples);
|
||||
glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
|
||||
&gets.max_transform_feedback_separate_attribs);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
|
||||
&gets.max_uniform_block_size);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
||||
&gets.max_uniform_buffer_bindings);
|
||||
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS,
|
||||
&gets.num_program_binary_formats);
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,
|
||||
&gets.uniform_buffer_offset_alignment);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
else {
|
||||
gets.max_anisotropy = 1;
|
||||
gets.max_draw_buffers = 1;
|
||||
gets.max_samples = 1;
|
||||
gets.max_transform_feedback_separate_attribs = 0;
|
||||
gets.max_uniform_block_size = 0;
|
||||
gets.max_uniform_buffer_bindings = 0;
|
||||
gets.num_program_binary_formats = 0;
|
||||
gets.uniform_buffer_offset_alignment = 0;
|
||||
gets.max_samples = 1;
|
||||
gets.max_draw_buffers = 1;
|
||||
gets.max_transform_feedback_separate_attribs = 0;
|
||||
gets.max_anisotropy = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
slog.v << "Feature level: " << +mFeatureLevel << '\n';
|
||||
slog.v << "Active workarounds: " << '\n';
|
||||
UTILS_NOUNROLL
|
||||
@@ -143,13 +146,29 @@ OpenGLContext::OpenGLContext() noexcept {
|
||||
|
||||
#ifndef NDEBUG
|
||||
// this is useful for development
|
||||
slog.v << "GL_MAX_DRAW_BUFFERS = " << gets.max_draw_buffers << '\n'
|
||||
<< "GL_MAX_RENDERBUFFER_SIZE = " << gets.max_renderbuffer_size << '\n'
|
||||
<< "GL_MAX_SAMPLES = " << gets.max_samples << '\n'
|
||||
<< "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = " << gets.max_anisotropy << '\n'
|
||||
<< "GL_MAX_UNIFORM_BLOCK_SIZE = " << gets.max_uniform_block_size << '\n'
|
||||
<< "GL_MAX_TEXTURE_IMAGE_UNITS = " << gets.max_texture_image_units << '\n'
|
||||
<< "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = " << gets.uniform_buffer_offset_alignment << '\n'
|
||||
slog.v
|
||||
<< "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = "
|
||||
<< gets.max_anisotropy << '\n'
|
||||
<< "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = "
|
||||
<< gets.max_combined_texture_image_units << '\n'
|
||||
<< "GL_MAX_DRAW_BUFFERS = "
|
||||
<< gets.max_draw_buffers << '\n'
|
||||
<< "GL_MAX_RENDERBUFFER_SIZE = "
|
||||
<< gets.max_renderbuffer_size << '\n'
|
||||
<< "GL_MAX_SAMPLES = "
|
||||
<< gets.max_samples << '\n'
|
||||
<< "GL_MAX_TEXTURE_IMAGE_UNITS = "
|
||||
<< gets.max_texture_image_units << '\n'
|
||||
<< "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = "
|
||||
<< gets.max_transform_feedback_separate_attribs << '\n'
|
||||
<< "GL_MAX_UNIFORM_BLOCK_SIZE = "
|
||||
<< gets.max_uniform_block_size << '\n'
|
||||
<< "GL_MAX_UNIFORM_BUFFER_BINDINGS = "
|
||||
<< gets.max_uniform_buffer_bindings << '\n'
|
||||
<< "GL_NUM_PROGRAM_BINARY_FORMATS = "
|
||||
<< gets.num_program_binary_formats << '\n'
|
||||
<< "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = "
|
||||
<< gets.uniform_buffer_offset_alignment << '\n'
|
||||
;
|
||||
flush(slog.v);
|
||||
#endif
|
||||
|
||||
@@ -153,14 +153,15 @@ public:
|
||||
// glGet*() values
|
||||
struct Gets {
|
||||
GLfloat max_anisotropy;
|
||||
GLint max_combined_texture_image_units;
|
||||
GLint max_draw_buffers;
|
||||
GLint max_renderbuffer_size;
|
||||
GLint max_samples;
|
||||
GLint max_uniform_block_size;
|
||||
GLint max_texture_image_units;
|
||||
GLint max_combined_texture_image_units;
|
||||
GLint max_transform_feedback_separate_attribs;
|
||||
GLint max_uniform_buffer_bindings;
|
||||
GLint max_uniform_block_size;
|
||||
GLint max_uniform_buffer_bindings;
|
||||
GLint num_program_binary_formats;
|
||||
GLint uniform_buffer_offset_alignment;
|
||||
} gets = {};
|
||||
|
||||
|
||||
@@ -147,8 +147,8 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
#endif
|
||||
|
||||
size_t const defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig const validConfig {
|
||||
.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize) };
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||
OpenGLDriver* const driver = new OpenGLDriver(ec, validConfig);
|
||||
return driver;
|
||||
}
|
||||
@@ -555,7 +555,8 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t size) {
|
||||
void OpenGLDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t size,
|
||||
utils::FixedSizeString<32> debugName) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
construct<GLSamplerGroup>(sbh, size);
|
||||
@@ -1289,7 +1290,9 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
checkDimensions(rt->gl.color[i], color[i].level);
|
||||
}
|
||||
}
|
||||
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
|
||||
if (UTILS_LIKELY(!getContext().isES2())) {
|
||||
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
#endif
|
||||
@@ -1450,6 +1453,11 @@ void OpenGLDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
|
||||
DEBUG_MARKER()
|
||||
if (sbh) {
|
||||
GLSamplerGroup* sb = handle_cast<GLSamplerGroup*>(sbh);
|
||||
for (auto& binding : mSamplerBindings) {
|
||||
if (binding == sb) {
|
||||
binding = nullptr;
|
||||
}
|
||||
}
|
||||
destruct(sbh, sb);
|
||||
}
|
||||
}
|
||||
@@ -1458,9 +1466,9 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
if (th) {
|
||||
auto& gl = mContext;
|
||||
GLTexture* t = handle_cast<GLTexture*>(th);
|
||||
if (UTILS_LIKELY(!t->gl.imported)) {
|
||||
auto& gl = mContext;
|
||||
if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) {
|
||||
gl.unbindTexture(t->gl.target, t->gl.id);
|
||||
if (UTILS_UNLIKELY(t->hwStream)) {
|
||||
@@ -1478,6 +1486,8 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
if (t->gl.sidecarRenderBufferMS) {
|
||||
glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
|
||||
}
|
||||
} else {
|
||||
gl.unbindTexture(t->gl.target, t->gl.id);
|
||||
}
|
||||
destruct(th, t);
|
||||
}
|
||||
@@ -1774,6 +1784,10 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
||||
// support more formats, but it requires querying GL_INTERNALFORMAT_SUPPORTED which is not
|
||||
// available in OpenGL ES.
|
||||
auto& gl = mContext;
|
||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||
auto [es2format, type] = textureFormatToFormatAndType(format);
|
||||
return es2format != GL_NONE && type != GL_NONE;
|
||||
}
|
||||
switch (format) {
|
||||
// Core formats.
|
||||
case TextureFormat::R8:
|
||||
@@ -2268,8 +2282,16 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
|
||||
return;
|
||||
}
|
||||
|
||||
GLenum const glFormat = getFormat(p.format);
|
||||
GLenum const glType = getType(p.type);
|
||||
GLenum glFormat;
|
||||
GLenum glType;
|
||||
if (mContext.isES2()) {
|
||||
auto formatAndType = textureFormatToFormatAndType(t->format);
|
||||
glFormat = formatAndType.first;
|
||||
glType = formatAndType.second;
|
||||
} else {
|
||||
glFormat = getFormat(p.format);
|
||||
glType = getType(p.type);
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!gl.isES2()) {
|
||||
|
||||
@@ -208,9 +208,11 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
|
||||
auto const& UTILS_RESTRICT usedBindingPoints = mUsedSamplerBindingPoints;
|
||||
|
||||
for (uint8_t i = 0, tmu = 0, n = mUsedBindingsCount; i < n; i++) {
|
||||
auto const binding = usedBindingPoints[i];
|
||||
size_t const binding = usedBindingPoints[i];
|
||||
assert_invariant(binding < Program::SAMPLER_BINDING_COUNT);
|
||||
auto const * const sb = samplerBindings[binding];
|
||||
assert_invariant(sb);
|
||||
if (!sb) continue; // should never happen, this would be a user error.
|
||||
for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here
|
||||
const GLTexture* const t = sb->textureUnitEntries[j].texture;
|
||||
if (t) { // program may not use all samplers of sampler group
|
||||
|
||||
@@ -140,6 +140,7 @@ 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) {
|
||||
}
|
||||
@@ -219,7 +220,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->attributes = std::move(program.getAttributes());
|
||||
}
|
||||
|
||||
token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program);
|
||||
token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program);
|
||||
if (token->gl.program) {
|
||||
return token;
|
||||
}
|
||||
@@ -249,7 +250,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
// We need to query the link status here to guarantee that the
|
||||
// program is compiled and linked now (we don't want this to be
|
||||
// deferred to later). We don't care about the result at this point.
|
||||
GLint status;
|
||||
GLint status = GL_FALSE;
|
||||
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||
programData.program = glProgram;
|
||||
|
||||
@@ -262,9 +263,9 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
// caching must be the last thing we do
|
||||
if (token->key) {
|
||||
if (token->key && status == GL_TRUE) {
|
||||
// Attempt to cache. This calls glGetProgramBinary.
|
||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, glProgram);
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -317,7 +318,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
// do this later, maybe depending on CPU usage?
|
||||
// attempt to cache if we don't have a thread pool (otherwise it's done
|
||||
// by the pool).
|
||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -431,7 +432,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
} else {
|
||||
// if we don't have a program yet, block until we get it.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include "CompilerThreadPool.h"
|
||||
#include "OpenGLBlobCache.h"
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/Program.h>
|
||||
@@ -95,6 +96,7 @@ public:
|
||||
|
||||
private:
|
||||
OpenGLDriver& mDriver;
|
||||
OpenGLBlobCache mBlobCache;
|
||||
CallbackManager mCallbackManager;
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
|
||||
|
||||
@@ -97,13 +97,32 @@ int PlatformEGL::getOSVersion() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool PlatformEGL::isOpenGL() const noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||
|
||||
EGLint major, minor;
|
||||
EGLBoolean const initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
|
||||
if (!initialized) {
|
||||
EGLDeviceEXT eglDevice;
|
||||
EGLint numDevices;
|
||||
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT =
|
||||
(PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");
|
||||
if (eglQueryDevicesEXT != nullptr) {
|
||||
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, 0);
|
||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!initialized)) {
|
||||
slog.e << "eglInitialize failed" << io::endl;
|
||||
return nullptr;
|
||||
@@ -148,10 +167,16 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
constexpr bool requestES2Context = false;
|
||||
#endif
|
||||
|
||||
// Request a ES2 context, devices that support ES3 will return an ES3 context
|
||||
Config contextAttribs = {
|
||||
{ EGL_CONTEXT_CLIENT_VERSION, 2 },
|
||||
};
|
||||
Config contextAttribs;
|
||||
|
||||
if (isOpenGL()) {
|
||||
// Request a OpenGL 4.1 context
|
||||
contextAttribs[EGL_CONTEXT_MAJOR_VERSION] = 4;
|
||||
contextAttribs[EGL_CONTEXT_MINOR_VERSION] = 1;
|
||||
} else {
|
||||
// Request a ES2 context, devices that support ES3 will return an ES3 context
|
||||
contextAttribs[EGL_CONTEXT_CLIENT_VERSION] = 2;
|
||||
}
|
||||
|
||||
// FOR TESTING ONLY, enforce the ES version we're asking for.
|
||||
// FIXME: we should check EGL_ANGLE_create_context_backwards_compatible, however, at least
|
||||
@@ -172,14 +197,17 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
// config use for creating the context
|
||||
EGLConfig eglConfig = EGL_NO_CONFIG_KHR;
|
||||
|
||||
// find a config we can use if we don't have "EGL_KHR_no_config_context" and that we can use
|
||||
// for the dummy pbuffer surface.
|
||||
mEGLConfig = findSwapChainConfig(0);
|
||||
if (UTILS_UNLIKELY(mEGLConfig == EGL_NO_CONFIG_KHR)) {
|
||||
goto error; // error already logged
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!ext.egl.KHR_no_config_context)) {
|
||||
// find a config we can use if we don't have "EGL_KHR_no_config_context" and that we can use
|
||||
// for the dummy pbuffer surface.
|
||||
mEGLConfig = findSwapChainConfig(
|
||||
SWAP_CHAIN_CONFIG_TRANSPARENT |
|
||||
SWAP_CHAIN_HAS_STENCIL_BUFFER,
|
||||
true, true);
|
||||
if (UTILS_UNLIKELY(mEGLConfig == EGL_NO_CONFIG_KHR)) {
|
||||
goto error; // error already logged
|
||||
}
|
||||
// if we don't have the EGL_KHR_no_config_context the context must be created with
|
||||
// the same config as the swapchain, so we have no choice but to create a
|
||||
// transparent config.
|
||||
@@ -333,22 +361,36 @@ void PlatformEGL::terminate() noexcept {
|
||||
eglReleaseThread();
|
||||
}
|
||||
|
||||
EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags) const {
|
||||
EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const {
|
||||
// Find config that support ES3.
|
||||
EGLConfig config = EGL_NO_CONFIG_KHR;
|
||||
EGLint configsCount;
|
||||
Config configAttribs = {
|
||||
{ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT },
|
||||
{ EGL_RED_SIZE, 8 },
|
||||
{ EGL_GREEN_SIZE, 8 },
|
||||
{ EGL_BLUE_SIZE, 8 },
|
||||
{ EGL_ALPHA_SIZE, (flags & SWAP_CHAIN_CONFIG_TRANSPARENT) ? 8 : 0 },
|
||||
{ EGL_DEPTH_SIZE, 24 },
|
||||
{ EGL_STENCIL_SIZE, (flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) ? 8 : 0 }
|
||||
};
|
||||
|
||||
if (!ext.egl.KHR_no_config_context) {
|
||||
if (isOpenGL()) {
|
||||
configAttribs[EGL_RENDERABLE_TYPE] = EGL_OPENGL_BIT;
|
||||
} else {
|
||||
configAttribs[EGL_RENDERABLE_TYPE] = EGL_OPENGL_ES2_BIT;
|
||||
if (ext.egl.KHR_create_context) {
|
||||
configAttribs[EGL_RENDERABLE_TYPE] |= EGL_OPENGL_ES3_BIT_KHR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ext.egl.KHR_create_context) {
|
||||
configAttribs[EGL_RECORDABLE_ANDROID] |= EGL_OPENGL_ES3_BIT_KHR;
|
||||
if (window) {
|
||||
configAttribs[EGL_SURFACE_TYPE] |= EGL_WINDOW_BIT;
|
||||
}
|
||||
|
||||
if (pbuffer) {
|
||||
configAttribs[EGL_SURFACE_TYPE] |= EGL_PBUFFER_BIT;
|
||||
}
|
||||
|
||||
if (ext.egl.ANDROID_recordable) {
|
||||
@@ -391,7 +433,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
|
||||
EGLConfig config = EGL_NO_CONFIG_KHR;
|
||||
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
|
||||
config = findSwapChainConfig(flags);
|
||||
config = findSwapChainConfig(flags, true, false);
|
||||
} else {
|
||||
config = mEGLConfig;
|
||||
}
|
||||
@@ -427,7 +469,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
|
||||
EGLConfig config = EGL_NO_CONFIG_KHR;
|
||||
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
|
||||
config = findSwapChainConfig(flags);
|
||||
config = findSwapChainConfig(flags, false, true);
|
||||
} else {
|
||||
config = mEGLConfig;
|
||||
}
|
||||
@@ -569,7 +611,7 @@ EGLint& PlatformEGL::Config::operator[](EGLint name) {
|
||||
auto pos = std::find_if(mConfig.begin(), mConfig.end(),
|
||||
[name](auto&& v) { return v.first == name; });
|
||||
if (pos == mConfig.end()) {
|
||||
mConfig.insert(pos - 1, { name, EGL_NONE });
|
||||
mConfig.insert(pos - 1, { name, 0 });
|
||||
pos = mConfig.end() - 2;
|
||||
}
|
||||
return pos->second;
|
||||
|
||||
@@ -185,7 +185,7 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) n
|
||||
AcquiredImage acquiredImage;
|
||||
EGLDisplay display;
|
||||
};
|
||||
Closure* closure = new Closure(source, mEGLDisplay);
|
||||
Closure* closure = new(std::nothrow) Closure(source, mEGLDisplay);
|
||||
auto patchedCallback = [](void* image, void* userdata) {
|
||||
Closure* closure = (Closure*)userdata;
|
||||
if (eglDestroyImageKHR(closure->display, (EGLImageKHR) image) == EGL_FALSE) {
|
||||
|
||||
@@ -30,21 +30,14 @@ using namespace utils;
|
||||
namespace filament {
|
||||
using namespace backend;
|
||||
|
||||
namespace glext {
|
||||
UTILS_PRIVATE PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = {};
|
||||
UTILS_PRIVATE PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = {};
|
||||
UTILS_PRIVATE PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR = {};
|
||||
UTILS_PRIVATE PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = {};
|
||||
UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = {};
|
||||
}
|
||||
using namespace glext;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLHeadless::PlatformEGLHeadless() noexcept
|
||||
: PlatformEGL() {
|
||||
}
|
||||
|
||||
bool PlatformEGLHeadless::isOpenGL() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API);
|
||||
@@ -58,166 +51,7 @@ backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Copied from the base class and modified slightly. Should be cleaned up/improved later.
|
||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||
|
||||
EGLint major, minor;
|
||||
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
|
||||
if (!initialized) {
|
||||
EGLDeviceEXT eglDevice;
|
||||
EGLint numDevices;
|
||||
|
||||
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT =
|
||||
(PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");
|
||||
if (eglQueryDevicesEXT != NULL) {
|
||||
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, 0);
|
||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!initialized)) {
|
||||
slog.e << "eglInitialize failed" << io::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
|
||||
|
||||
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
|
||||
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
|
||||
eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC) eglGetProcAddress("eglClientWaitSyncKHR");
|
||||
|
||||
eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
|
||||
eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
|
||||
|
||||
EGLint configsCount;
|
||||
|
||||
EGLint configAttribs[] = {
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 0,
|
||||
EGL_DEPTH_SIZE, 32,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLint contextAttribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE, EGL_NONE, // reserved for EGL_CONTEXT_OPENGL_NO_ERROR_KHR below
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLint pbufferAttribs[] = {
|
||||
EGL_WIDTH, 1,
|
||||
EGL_HEIGHT, 1,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
#ifdef NDEBUG
|
||||
// When we don't have a shared context and we're in release mode, we always activate the
|
||||
// EGL_KHR_create_context_no_error extension.
|
||||
if (!sharedContext && extensions.has("EGL_KHR_create_context_no_error")) {
|
||||
contextAttribs[2] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR;
|
||||
contextAttribs[3] = EGL_TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
EGLConfig eglConfig = nullptr;
|
||||
|
||||
// find an opaque config
|
||||
if (!eglChooseConfig(mEGLDisplay, configAttribs, &mEGLConfig, 1, &configsCount)) {
|
||||
logEglError("eglChooseConfig");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// fallback to a 24-bit depth buffer
|
||||
if (configsCount == 0) {
|
||||
configAttribs[10] = EGL_DEPTH_SIZE;
|
||||
configAttribs[11] = 24;
|
||||
|
||||
if (!eglChooseConfig(mEGLDisplay, configAttribs, &mEGLConfig, 1, &configsCount)) {
|
||||
logEglError("eglChooseConfig");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
// find a transparent config
|
||||
configAttribs[8] = EGL_ALPHA_SIZE;
|
||||
configAttribs[9] = 8;
|
||||
if (!eglChooseConfig(mEGLDisplay, configAttribs, &mEGLTransparentConfig, 1, &configsCount) ||
|
||||
(configAttribs[13] == EGL_DONT_CARE && configsCount == 0)) {
|
||||
logEglError("eglChooseConfig");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!extensions.has("EGL_KHR_no_config_context")) {
|
||||
// if we have the EGL_KHR_no_config_context, we don't need to worry about the config
|
||||
// when creating the context, otherwise, we must always pick a transparent config.
|
||||
eglConfig = mEGLConfig = mEGLTransparentConfig;
|
||||
}
|
||||
|
||||
// the pbuffer dummy surface is always created with a transparent surface because
|
||||
// either we have EGL_KHR_no_config_context and it doesn't matter, or we don't and
|
||||
// we must use a transparent surface
|
||||
mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLTransparentConfig, pbufferAttribs);
|
||||
if (mEGLDummySurface == EGL_NO_SURFACE) {
|
||||
logEglError("eglCreatePbufferSurface");
|
||||
goto error;
|
||||
}
|
||||
|
||||
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig, (EGLContext)sharedContext, contextAttribs);
|
||||
if (mEGLContext == EGL_NO_CONTEXT && sharedContext &&
|
||||
extensions.has("EGL_KHR_create_context_no_error")) {
|
||||
// context creation could fail because of EGL_CONTEXT_OPENGL_NO_ERROR_KHR
|
||||
// not matching the sharedContext. Try with it.
|
||||
contextAttribs[2] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR;
|
||||
contextAttribs[3] = EGL_TRUE;
|
||||
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig, (EGLContext)sharedContext, contextAttribs);
|
||||
}
|
||||
if (UTILS_UNLIKELY(mEGLContext == EGL_NO_CONTEXT)) {
|
||||
// eglCreateContext failed
|
||||
logEglError("eglCreateContext");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!makeCurrent(mEGLDummySurface, mEGLDummySurface)) {
|
||||
// eglMakeCurrent failed
|
||||
logEglError("eglMakeCurrent");
|
||||
goto error;
|
||||
}
|
||||
|
||||
initializeGlExtensions();
|
||||
|
||||
clearGlError();
|
||||
|
||||
// success!!
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedContext, driverConfig);
|
||||
|
||||
error:
|
||||
// if we're here, we've failed
|
||||
if (mEGLDummySurface) {
|
||||
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
|
||||
}
|
||||
if (mEGLContext) {
|
||||
eglDestroyContext(mEGLDisplay, mEGLContext);
|
||||
}
|
||||
|
||||
mEGLDummySurface = EGL_NO_SURFACE;
|
||||
mEGLContext = EGL_NO_CONTEXT;
|
||||
|
||||
eglTerminate(mEGLDisplay);
|
||||
eglReleaseThread();
|
||||
|
||||
return nullptr;
|
||||
return PlatformEGL::createDriver(sharedContext, driverConfig);
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -168,7 +168,7 @@ error:
|
||||
}
|
||||
|
||||
bool PlatformWGL::isExtraContextSupported() const noexcept {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlatformWGL::createContext(bool shared) {
|
||||
|
||||
@@ -40,16 +40,20 @@ namespace {
|
||||
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
|
||||
const VkExtent2D srcExtent, VulkanAttachment src, VulkanAttachment dst,
|
||||
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
const VkImageBlit blitRegions[1] = {{.srcSubresource = {aspect, src.level, src.layer, 1},
|
||||
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]}}};
|
||||
.dstOffsets = {dstRect[0], dstRect[1]},
|
||||
}};
|
||||
|
||||
const VkImageResolve resolveRegions[1] = {{.srcSubresource = {aspect, src.level, src.layer, 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}}};
|
||||
.extent = {srcExtent.width, srcExtent.height, 1},
|
||||
}};
|
||||
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
@@ -69,11 +73,14 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< "layout=" << src.getLayout()
|
||||
<< " layout=" << src.getLayout()
|
||||
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
|
||||
<< "layout=" << dst.getLayout() << utils::io::endl;
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
}
|
||||
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
|
||||
@@ -91,17 +98,14 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
1, blitRegions, filter);
|
||||
}
|
||||
|
||||
VulkanLayout newSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
|
||||
VulkanLayout const newDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
|
||||
|
||||
// In the case of blitting the depth attachment, we transition the source into GENERAL (for
|
||||
// sampling) and set the copy as ATTACHMENT_OPTIMAL (to be set as the attachment).
|
||||
if (any(src.texture->usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
newSrcLayout = VulkanLayout::DEPTH_SAMPLER;
|
||||
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
|
||||
oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
|
||||
}
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, newSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, newDstLayout);
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
|
||||
}
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
|
||||
}
|
||||
|
||||
struct BlitterUniforms {
|
||||
@@ -190,19 +194,19 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
}
|
||||
|
||||
void VulkanBlitter::terminate() noexcept {
|
||||
if (mDevice) {
|
||||
if (mDepthResolveProgram) {
|
||||
delete mDepthResolveProgram;
|
||||
mDepthResolveProgram = nullptr;
|
||||
}
|
||||
|
||||
if (mTriangleBuffer) {
|
||||
delete mTriangleBuffer;
|
||||
mTriangleBuffer = nullptr;
|
||||
}
|
||||
if (mTriangleBuffer) {
|
||||
delete mTriangleBuffer;
|
||||
mTriangleBuffer = nullptr;
|
||||
}
|
||||
|
||||
if (mParamsBuffer) {
|
||||
delete mParamsBuffer;
|
||||
mParamsBuffer = nullptr;
|
||||
}
|
||||
if (mParamsBuffer) {
|
||||
delete mParamsBuffer;
|
||||
mParamsBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,17 +237,18 @@ void VulkanBlitter::lazyInit() noexcept {
|
||||
|
||||
VkShaderModule vertexShader = decode(VKSHADERS_BLITDEPTHVS_DATA, VKSHADERS_BLITDEPTHVS_SIZE);
|
||||
VkShaderModule fragmentShader = decode(VKSHADERS_BLITDEPTHFS_DATA, VKSHADERS_BLITDEPTHFS_SIZE);
|
||||
mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader);
|
||||
|
||||
// Allocate one anonymous sampler at slot 0.
|
||||
mDepthResolveProgram->samplerGroupInfo[0].samplers.reserve(1);
|
||||
mDepthResolveProgram->samplerGroupInfo[0].samplers.resize(1);
|
||||
VulkanProgram::CustomSamplerInfoList samplers = {
|
||||
{0, 0, ShaderStageFlags::FRAGMENT},
|
||||
};
|
||||
mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader, samplers);
|
||||
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Created Shader Module for VulkanBlitter "
|
||||
<< "shaders = (" << vertexShader << ", " << fragmentShader << ")"
|
||||
<< utils::io::endl;
|
||||
}
|
||||
#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,
|
||||
@@ -355,7 +360,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
||||
// DRAW THE TRIANGLE
|
||||
// -----------------
|
||||
|
||||
mPipelineCache.bindProgram(*mDepthResolveProgram);
|
||||
mPipelineCache.bindProgram(mDepthResolveProgram);
|
||||
mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
|
||||
|
||||
auto vkraster = mPipelineCache.getCurrentRasterState();
|
||||
@@ -377,13 +382,19 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
||||
vkraster.colorTargetCount = 0;
|
||||
mPipelineCache.bindRasterState(vkraster);
|
||||
|
||||
VulkanPipelineCache::VertexArray varray = {};
|
||||
VkBuffer buffers[1] = {};
|
||||
VkDeviceSize offsets[1] = {};
|
||||
buffers[0] = mTriangleBuffer->getGpuBuffer();
|
||||
varray.attributes[0] = { .location = 0, .binding = 0, .format = VK_FORMAT_R32G32_SFLOAT };
|
||||
varray.buffers[0] = { .binding = 0, .stride = sizeof(float) * 2 };
|
||||
mPipelineCache.bindVertexArray(varray);
|
||||
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({});
|
||||
|
||||
@@ -83,7 +83,7 @@ struct VulkanCommandBuffer {
|
||||
}
|
||||
|
||||
inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) {
|
||||
mResourceManager.acquire(srcResources);
|
||||
mResourceManager.acquireAll(srcResources);
|
||||
}
|
||||
|
||||
inline void reset() {
|
||||
|
||||
@@ -64,21 +64,12 @@ VkImageView VulkanAttachment::getImageView(VkImageAspectFlags aspect) {
|
||||
|
||||
VkImageSubresourceRange VulkanAttachment::getSubresourceRange(VkImageAspectFlags aspect) const {
|
||||
assert_invariant(texture);
|
||||
uint32_t levelCount = 1;
|
||||
uint32_t layerCount = 1;
|
||||
// For depth attachments, we consider all the subresource range since layout transitions of
|
||||
// depth and stencil attachments should always be carried out for all subresources.
|
||||
if (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||
auto range = texture->getPrimaryRange();
|
||||
levelCount = range.levelCount;
|
||||
layerCount = range.layerCount;
|
||||
}
|
||||
return {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = uint32_t(level),
|
||||
.levelCount = levelCount,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = uint32_t(layer),
|
||||
.layerCount = layerCount,
|
||||
.layerCount = 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/bitset.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -100,8 +102,8 @@ public:
|
||||
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
||||
}
|
||||
|
||||
inline VkFormat getDepthFormat() const {
|
||||
return mDepthFormat;
|
||||
inline VkFormatList const& getAttachmentDepthFormats() const {
|
||||
return mDepthFormats;
|
||||
}
|
||||
|
||||
inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept {
|
||||
@@ -130,7 +132,7 @@ private:
|
||||
bool mDebugMarkersSupported = false;
|
||||
bool mDebugUtilsSupported = false;
|
||||
|
||||
VkFormat mDepthFormat;
|
||||
VkFormatList mDepthFormats;
|
||||
|
||||
// For convenience so that VulkanPlatform can initialize the private fields.
|
||||
friend class VulkanPlatform;
|
||||
|
||||
@@ -156,7 +156,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mThreadSafeResourceManager(&mResourceAllocator),
|
||||
mPipelineCache(&mResourceAllocator),
|
||||
mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache),
|
||||
mReadPixels(mPlatform->getDevice()) {
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
UTILS_UNUSED const PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback
|
||||
@@ -214,8 +215,8 @@ Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& cont
|
||||
Platform::DriverConfig const& driverConfig) noexcept {
|
||||
assert_invariant(platform);
|
||||
size_t defaultSize = FVK_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig{
|
||||
.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize)};
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||
return new VulkanDriver(platform, context, validConfig);
|
||||
}
|
||||
|
||||
@@ -290,9 +291,11 @@ void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
}
|
||||
|
||||
void VulkanDriver::endFrame(uint32_t frameId) {
|
||||
if (mCommands->flush()) {
|
||||
collectGarbage();
|
||||
}
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("endframe");
|
||||
mCommands->flush();
|
||||
collectGarbage();
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::flush(int) {
|
||||
@@ -313,7 +316,8 @@ void VulkanDriver::finish(int dummy) {
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count) {
|
||||
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count,
|
||||
utils::FixedSizeString<32> debugName) {
|
||||
auto sg = mResourceAllocator.construct<VulkanSamplerGroup>(sbh, count);
|
||||
mResourceManager.acquire(sg);
|
||||
}
|
||||
@@ -370,7 +374,7 @@ void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
||||
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage) {
|
||||
auto bufferObject = mResourceAllocator.construct<VulkanBufferObject>(boh, mAllocator,
|
||||
mStagePool, byteCount, bindingType, usage);
|
||||
mStagePool, byteCount, bindingType);
|
||||
mResourceManager.acquire(bufferObject);
|
||||
}
|
||||
|
||||
@@ -703,10 +707,6 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
// the GPU supports the given texture format with non-zero optimal tiling features.
|
||||
bool VulkanDriver::isTextureFormatSupported(TextureFormat format) {
|
||||
VkFormat vkformat = getVkFormat(format);
|
||||
// We automatically use an alternative format when the client requests DEPTH24.
|
||||
if (format == TextureFormat::DEPTH24) {
|
||||
vkformat = mContext.getDepthFormat();
|
||||
}
|
||||
if (vkformat == VK_FORMAT_UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
@@ -734,10 +734,6 @@ bool VulkanDriver::isTextureFormatMipmappable(TextureFormat format) {
|
||||
|
||||
bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
||||
VkFormat vkformat = getVkFormat(format);
|
||||
// We automatically use an alternative format when the client requests DEPTH24.
|
||||
if (format == TextureFormat::DEPTH24) {
|
||||
vkformat = mContext.getDepthFormat();
|
||||
}
|
||||
if (vkformat == VK_FORMAT_UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
@@ -763,7 +759,7 @@ bool VulkanDriver::isAutoDepthResolveSupported() {
|
||||
}
|
||||
|
||||
bool VulkanDriver::isSRGBSwapChainSupported() {
|
||||
return mPlatform->isSRGBSwapChainSupported();
|
||||
return mIsSRGBSwapChainSupported;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isStereoSupported() {
|
||||
@@ -1019,7 +1015,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
// If that's the case, we need to change the layout of the texture to DEPTH_SAMPLER, which is a
|
||||
// more general layout. Otherwise, we prefer the DEPTH_ATTACHMENT layout, which is optimal for
|
||||
// the non-sampling case.
|
||||
bool samplingDepthAttachment = false;
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
|
||||
@@ -1039,29 +1034,24 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
if (!any(texture->usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
continue;
|
||||
}
|
||||
samplingDepthAttachment
|
||||
= depth.texture && texture->getVkImage() == depth.texture->getVkImage();
|
||||
if (texture->getPrimaryImageLayout() == VulkanLayout::DEPTH_SAMPLER) {
|
||||
continue;
|
||||
}
|
||||
VkImageSubresourceRange const subresources{
|
||||
.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = texture->levels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = texture->depth,
|
||||
};
|
||||
commands.acquire(texture);
|
||||
texture->transitionLayout(cmdbuffer, subresources, VulkanLayout::DEPTH_SAMPLER);
|
||||
|
||||
// Transition the primary view, which is the sampler's view into the right layout.
|
||||
texture->transitionLayout(cmdbuffer, texture->getPrimaryViewRange(),
|
||||
VulkanLayout::DEPTH_SAMPLER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// currentDepthLayout tracks state of the layout after the (potential) transition in the above block.
|
||||
VulkanLayout currentDepthLayout = depth.getLayout();
|
||||
VulkanLayout const renderPassDepthLayout = samplingDepthAttachment
|
||||
? VulkanLayout::DEPTH_SAMPLER
|
||||
: VulkanLayout::DEPTH_ATTACHMENT;
|
||||
|
||||
VulkanLayout const currentDepthLayout = depth.getLayout();
|
||||
VulkanLayout const renderPassDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
// We need to keep the final layout as an attachment because the implicit transition does not
|
||||
// have any barrier guarrantees, meaning that if we want to sample from the output in the next
|
||||
// pass, then we'd have a race-condition/validation error.
|
||||
VulkanLayout const finalDepthLayout = renderPassDepthLayout;
|
||||
|
||||
TargetBufferFlags clearVal = params.flags.clear;
|
||||
@@ -1071,11 +1061,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
discardEndVal &= ~TargetBufferFlags::DEPTH;
|
||||
clearVal &= ~TargetBufferFlags::DEPTH;
|
||||
}
|
||||
if (currentDepthLayout != renderPassDepthLayout) {
|
||||
depth.texture->transitionLayout(cmdbuffer,
|
||||
depth.getSubresourceRange(VK_IMAGE_ASPECT_DEPTH_BIT), renderPassDepthLayout);
|
||||
currentDepthLayout = renderPassDepthLayout;
|
||||
}
|
||||
auto const attachmentSubresourceRange = depth.getSubresourceRange(VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
depth.texture->setLayout(attachmentSubresourceRange, VulkanLayout::DEPTH_ATTACHMENT);
|
||||
}
|
||||
|
||||
// Create the VkRenderPass or fetch it from cache.
|
||||
@@ -1269,12 +1256,11 @@ void VulkanDriver::endRenderPass(int) {
|
||||
// NOTE: ideally dstStageMask would merely be VERTEX_SHADER_BIT | FRAGMENT_SHADER_BIT, but this
|
||||
// seems to be insufficient on Mali devices. To work around this we are adding a more aggressive
|
||||
// TOP_OF_PIPE barrier.
|
||||
|
||||
if (!rt->isSwapChain()) {
|
||||
VkMemoryBarrier barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
VkMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
};
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
if (rt->hasDepth()) {
|
||||
@@ -1370,10 +1356,6 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||
|
||||
VulkanSwapChain* swapChain = mResourceAllocator.handle_cast<VulkanSwapChain*>(sch);
|
||||
|
||||
if (mCommands->flush()) {
|
||||
collectGarbage();
|
||||
}
|
||||
|
||||
// Present the backbuffer after the most recent command buffer submission has finished.
|
||||
swapChain->present();
|
||||
FVK_SYSTRACE_END();
|
||||
@@ -1442,7 +1424,6 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
|
||||
srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(),
|
||||
std::move(pbd),
|
||||
[&context = mContext](uint32_t reqs, VkFlags flags) {
|
||||
utils::slog.d <<"read pixels: reqs=" << reqs <<" flags=" << flags << utils::io::endl;
|
||||
return context.selectMemoryType(reqs, flags);
|
||||
},
|
||||
[this](PixelBufferDescriptor&& pbd) {
|
||||
@@ -1554,56 +1535,17 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
mPipelineCache.setCurrentRasterState(vkraster);
|
||||
|
||||
// Declare fixed-size arrays that get passed to the pipeCache and to vkCmdBindVertexBuffers.
|
||||
VulkanPipelineCache::VertexArray varray = {};
|
||||
VkBuffer buffers[MAX_VERTEX_ATTRIBUTE_COUNT] = {};
|
||||
VkDeviceSize offsets[MAX_VERTEX_ATTRIBUTE_COUNT] = {};
|
||||
|
||||
// For each attribute, append to each of the above lists.
|
||||
const uint32_t bufferCount = prim.vertexBuffer->attributes.size();
|
||||
for (uint32_t attribIndex = 0; attribIndex < bufferCount; attribIndex++) {
|
||||
Attribute attrib = prim.vertexBuffer->attributes[attribIndex];
|
||||
|
||||
const bool isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
const bool isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
|
||||
|
||||
VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger);
|
||||
|
||||
// HACK: Re-use the positions buffer as a dummy buffer for disabled attributes. Filament's
|
||||
// vertex shaders declare all attributes as either vec4 or uvec4 (the latter for bone
|
||||
// indices), and positions are always at least 32 bits per element. Therefore we can assign
|
||||
// a dummy type of either R8G8B8A8_UINT or R8G8B8A8_SNORM, depending on whether the shader
|
||||
// expects to receive floats or ints.
|
||||
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
|
||||
vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM;
|
||||
attrib = prim.vertexBuffer->attributes[0];
|
||||
}
|
||||
|
||||
const VulkanBuffer* buffer = prim.vertexBuffer->buffers[attrib.buffer];
|
||||
|
||||
// If the vertex buffer is missing a constituent buffer object, skip the draw call.
|
||||
// There is no need to emit an error message because this is not explicitly forbidden.
|
||||
if (buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffers[attribIndex] = buffer->getGpuBuffer();
|
||||
offsets[attribIndex] = attrib.offset;
|
||||
varray.attributes[attribIndex] = {
|
||||
.location = attribIndex, // matches the GLSL layout specifier
|
||||
.binding = attribIndex, // matches the position within vkCmdBindVertexBuffers
|
||||
.format = vkformat,
|
||||
};
|
||||
varray.buffers[attribIndex] = {
|
||||
.binding = attribIndex,
|
||||
.stride = attrib.stride,
|
||||
};
|
||||
}
|
||||
uint32_t const bufferCount = prim.vertexBuffer->attributes.size();
|
||||
VkVertexInputAttributeDescription const* attribDesc = prim.vertexBuffer->getAttribDescriptions();
|
||||
VkVertexInputBindingDescription const* bufferDesc = prim.vertexBuffer->getBufferDescriptions();
|
||||
VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers();
|
||||
VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets();
|
||||
|
||||
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
|
||||
mPipelineCache.bindProgram(*program);
|
||||
mPipelineCache.bindProgram(program);
|
||||
mPipelineCache.bindRasterState(mPipelineCache.getCurrentRasterState());
|
||||
mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology);
|
||||
mPipelineCache.bindVertexArray(varray);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount);
|
||||
|
||||
// Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding),
|
||||
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
|
||||
@@ -1612,54 +1554,69 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||
|
||||
VulkanPipelineCache::UsageFlags usage;
|
||||
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
|
||||
VulkanPipelineCache::UsageFlags usage = program->getUsage();
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT; samplerGroupIdx++) {
|
||||
const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx];
|
||||
const auto& samplers = samplerGroup.samplers;
|
||||
if (samplers.empty()) {
|
||||
for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) {
|
||||
uint16_t const indexPair = bindingToSamplerIndex[binding];
|
||||
|
||||
if (indexPair == 0xffff) {
|
||||
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
|
||||
continue;
|
||||
}
|
||||
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx];
|
||||
|
||||
uint16_t const samplerGroupInd = (indexPair >> 8) & 0xff;
|
||||
uint16_t const samplerInd = (indexPair & 0xff);
|
||||
|
||||
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd];
|
||||
if (!vksb) {
|
||||
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
|
||||
continue;
|
||||
}
|
||||
SamplerGroup* sb = vksb->sb.get();
|
||||
assert_invariant(sb->getSize() == samplers.size());
|
||||
size_t samplerIdx = 0;
|
||||
for (auto& sampler : samplers) {
|
||||
const SamplerDescriptor* boundSampler = sb->data() + samplerIdx;
|
||||
samplerIdx++;
|
||||
SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd;
|
||||
|
||||
if (UTILS_LIKELY(boundSampler->t)) {
|
||||
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
|
||||
|
||||
// TODO: can this uninitialized check be checked in a higher layer?
|
||||
// This fallback path is very flaky because the dummy texture might not have
|
||||
// matching characteristics. (e.g. if the missing texture is a 3D texture)
|
||||
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'";
|
||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
|
||||
#endif
|
||||
texture = mEmptyTexture.get();
|
||||
}
|
||||
|
||||
const SamplerParams& samplerParams = boundSampler->s;
|
||||
VkSampler vksampler = mSamplerCache.getSampler(samplerParams);
|
||||
|
||||
usage = VulkanPipelineCache::getUsageFlags(sampler.binding, samplerGroup.stageFlags, usage);
|
||||
|
||||
samplerInfo[sampler.binding] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = texture->getPrimaryImageView(),
|
||||
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
|
||||
};
|
||||
samplerTextures[sampler.binding] = texture;
|
||||
}
|
||||
if (UTILS_UNLIKELY(!boundSampler->t)) {
|
||||
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
|
||||
continue;
|
||||
}
|
||||
|
||||
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
|
||||
// TODO: can this uninitialized check be checked in a higher layer?
|
||||
// This fallback path is very flaky because the dummy texture might not have
|
||||
// matching characteristics. (e.g. if the missing texture is a 3D texture)
|
||||
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'";
|
||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
|
||||
#endif
|
||||
texture = mEmptyTexture.get();
|
||||
}
|
||||
|
||||
SamplerParams const& samplerParams = boundSampler->s;
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(samplerParams);
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
|
||||
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
|
||||
// an attachment, then the sampler for this texture has the same view properties as a
|
||||
// view for an attachment. Therefore, we can use getAttachmentView to get a
|
||||
// corresponding VkImageView.
|
||||
imageView = texture->getAttachmentView(range);
|
||||
} else {
|
||||
imageView = texture->getViewForType(range, expectedType);
|
||||
}
|
||||
|
||||
samplerInfo[binding] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = imageView,
|
||||
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
|
||||
};
|
||||
samplerTextures[binding] = texture;
|
||||
}
|
||||
|
||||
mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
|
||||
|
||||
@@ -112,6 +112,8 @@ private:
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
VulkanReadPixels mReadPixels;
|
||||
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -49,88 +49,100 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
|
||||
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
|
||||
: HwProgram(builder.getName()),
|
||||
VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo(builder.getSpecializationConstants().size())),
|
||||
mDevice(device) {
|
||||
auto const& blobs = builder.getShadersSource();
|
||||
VkShaderModule* modules[2] = {&bundle.vertex, &bundle.fragment};
|
||||
// TODO: handle compute shaders.
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
auto& blobs = builder.getShadersSource();
|
||||
auto& modules = mInfo->shaders;
|
||||
for (size_t i = 0; i < MAX_SHADER_MODULES; i++) {
|
||||
const auto& blob = blobs[i];
|
||||
VkShaderModule* module = modules[i];
|
||||
VkShaderModuleCreateInfo moduleInfo = {};
|
||||
moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleInfo.codeSize = blob.size();
|
||||
moduleInfo.pCode = (uint32_t*) blob.data();
|
||||
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, module);
|
||||
uint32_t* data = (uint32_t*)blob.data();
|
||||
VkShaderModule& module = modules[i];
|
||||
VkShaderModuleCreateInfo moduleInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
.codeSize = blob.size(),
|
||||
.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();
|
||||
if (!specializationConstants.empty()) {
|
||||
// Allocate a single heap block to store all the specialization constants structures
|
||||
// our supported types are int32, float and bool, so we use 4 bytes per data. bool will
|
||||
// just use the first byte.
|
||||
char* pStorage = (char*)malloc(
|
||||
sizeof(VkSpecializationInfo) +
|
||||
specializationConstants.size() * sizeof(VkSpecializationMapEntry) +
|
||||
specializationConstants.size() * 4);
|
||||
|
||||
VkSpecializationInfo* const pInfo = (VkSpecializationInfo*)pStorage;
|
||||
VkSpecializationMapEntry* const pEntries =
|
||||
(VkSpecializationMapEntry*)(pStorage + sizeof(VkSpecializationInfo));
|
||||
void* pData = pStorage + sizeof(VkSpecializationInfo) +
|
||||
specializationConstants.size() * sizeof(VkSpecializationMapEntry);
|
||||
|
||||
*pInfo = {
|
||||
.mapEntryCount = specializationConstants.size(),
|
||||
.pMapEntries = pEntries,
|
||||
.dataSize = specializationConstants.size() * 4,
|
||||
.pData = pData,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < specializationConstants.size(); i++) {
|
||||
uint32_t const offset = uint32_t(i) * 4;
|
||||
pEntries[i] = {
|
||||
.constantID = specializationConstants[i].id,
|
||||
.offset = offset,
|
||||
// Note that bools are 4-bytes in Vulkan
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
|
||||
.size = 4,
|
||||
};
|
||||
|
||||
using SpecConstant = Program::SpecializationConstant::Type;
|
||||
char const* addr = (char*)pData + 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;
|
||||
for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) {
|
||||
auto const& group = groupInfo[groupInd];
|
||||
auto const& samplers = group.samplers;
|
||||
for (size_t i = 0; i < samplers.size(); ++i) {
|
||||
uint32_t const binding = samplers[i].binding;
|
||||
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
|
||||
usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage);
|
||||
}
|
||||
bundle.specializationInfos = pInfo;
|
||||
}
|
||||
|
||||
// Make a copy of the binding map
|
||||
samplerGroupInfo = builder.getSamplerGroupInfo();
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex
|
||||
<< ", " << bundle.fragment << ")" << utils::io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
|
||||
CustomSamplerInfoList const& samplerInfo) noexcept
|
||||
: VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo(0)),
|
||||
mDevice(device) {
|
||||
bundle.vertex = vs;
|
||||
bundle.fragment = fs;
|
||||
mInfo->shaders[0] = vs;
|
||||
mInfo->shaders[1] = fs;
|
||||
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
|
||||
auto& usage = mInfo->usage;
|
||||
bindingToSamplerIndex.resize(samplerInfo.size());
|
||||
for (uint16_t binding = 0; binding < samplerInfo.size(); ++binding) {
|
||||
auto const& sampler = samplerInfo[binding];
|
||||
bindingToSamplerIndex[binding]
|
||||
= (sampler.groupIndex << 8) | (0xff & sampler.samplerIndex);
|
||||
usage = VulkanPipelineCache::getUsageFlags(binding, sampler.flags, usage);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanProgram::~VulkanProgram() {
|
||||
vkDestroyShaderModule(mDevice, bundle.vertex, VKALLOC);
|
||||
vkDestroyShaderModule(mDevice, bundle.fragment, VKALLOC);
|
||||
free(bundle.specializationInfos);
|
||||
for (auto shader: mInfo->shaders) {
|
||||
vkDestroyShaderModule(mDevice, shader, VKALLOC);
|
||||
}
|
||||
delete mInfo;
|
||||
}
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
@@ -276,16 +288,62 @@ VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool&
|
||||
uint32_t elementCount, AttributeArray const& attribs)
|
||||
: HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||
buffers(bufferCount, nullptr),
|
||||
mResources(allocator) {}
|
||||
mInfo(new PipelineInfo(attribs.size())),
|
||||
mResources(allocator) {
|
||||
auto attribDesc = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
auto bufferDesc = mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
auto offsets = mInfo->mSoa.data<PipelineInfo::OFFSETS>();
|
||||
auto attribToBufferIndex = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
std::fill(mInfo->mSoa.begin<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(),
|
||||
mInfo->mSoa.end<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(), -1);
|
||||
|
||||
for (uint32_t attribIndex = 0; attribIndex < attribs.size(); attribIndex++) {
|
||||
Attribute attrib = attribs[attribIndex];
|
||||
bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
|
||||
VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger);
|
||||
|
||||
// HACK: Re-use the positions buffer as a dummy buffer for disabled attributes. Filament's
|
||||
// vertex shaders declare all attributes as either vec4 or uvec4 (the latter for bone
|
||||
// indices), and positions are always at least 32 bits per element. Therefore we can assign
|
||||
// a dummy type of either R8G8B8A8_UINT or R8G8B8A8_SNORM, depending on whether the shader
|
||||
// expects to receive floats or ints.
|
||||
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
|
||||
vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM;
|
||||
attrib = attribs[0];
|
||||
}
|
||||
offsets[attribIndex] = attrib.offset;
|
||||
attribDesc[attribIndex] = {
|
||||
.location = attribIndex,// matches the GLSL layout specifier
|
||||
.binding = attribIndex, // matches the position within vkCmdBindVertexBuffers
|
||||
.format = vkformat,
|
||||
};
|
||||
bufferDesc[attribIndex] = {
|
||||
.binding = attribIndex,
|
||||
.stride = attrib.stride,
|
||||
};
|
||||
attribToBufferIndex[attribIndex] = attrib.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
VulkanVertexBuffer::~VulkanVertexBuffer() {
|
||||
delete mInfo;
|
||||
}
|
||||
|
||||
void VulkanVertexBuffer::setBuffer(VulkanBufferObject* bufferObject, uint32_t index) {
|
||||
buffers[index] = &bufferObject->buffer;
|
||||
size_t count = attributes.size();
|
||||
auto vkbuffers = mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
|
||||
auto attribToBuffer = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) {
|
||||
if (attribToBuffer[attribIndex] == static_cast<int8_t>(index)) {
|
||||
vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer();
|
||||
}
|
||||
}
|
||||
mResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage)
|
||||
uint32_t byteCount, BufferObjectBinding bindingType)
|
||||
: HwBufferObject(byteCount),
|
||||
VulkanResource(VulkanResourceType::BUFFER_OBJECT),
|
||||
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
||||
|
||||
@@ -30,20 +30,71 @@
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/StructureOfArrays.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanTimestamps;
|
||||
|
||||
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
VulkanProgram(VkDevice device, const Program& builder) noexcept;
|
||||
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept;
|
||||
|
||||
struct CustomSamplerInfo {
|
||||
uint8_t groupIndex;
|
||||
uint8_t samplerIndex;
|
||||
ShaderStageFlags flags;
|
||||
};
|
||||
using CustomSamplerInfoList = utils::FixedCapacityVector<CustomSamplerInfo>;
|
||||
|
||||
// We allow custom descriptor of the samplers within shaders. This is needed if we want to use
|
||||
// a program that exists only in the backend - for example, for shader-based bliting.
|
||||
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
|
||||
CustomSamplerInfoList const& samplerInfo) noexcept;
|
||||
~VulkanProgram();
|
||||
VulkanPipelineCache::ProgramBundle bundle;
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
|
||||
inline VkShaderModule getVertexShader() const {
|
||||
return mInfo->shaders[0];
|
||||
}
|
||||
|
||||
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
|
||||
|
||||
inline VulkanPipelineCache::UsageFlags getUsage() const { return mInfo->usage; }
|
||||
|
||||
inline utils::FixedCapacityVector<uint16_t> const& getBindingToSamplerIndex() const {
|
||||
return mInfo->bindingToSamplerIndex;
|
||||
}
|
||||
|
||||
inline VkSpecializationInfo const& getSpecConstInfo() const {
|
||||
return mInfo->specializationInfo;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
// 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;
|
||||
|
||||
struct PipelineInfo {
|
||||
PipelineInfo(size_t specConstsCount) :
|
||||
bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
|
||||
specConsts(specConstsCount, VkSpecializationMapEntry{}),
|
||||
specConstData(new char[specConstsCount * 4])
|
||||
{}
|
||||
|
||||
// This bitset maps to each of the sampler in the sampler groups associated with this
|
||||
// program, and whether each sampler is used in which shader (i.e. vert, frag, compute).
|
||||
VulkanPipelineCache::UsageFlags usage;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
// The render target bundles together a set of attachments, each of which can have one of the
|
||||
@@ -94,12 +145,51 @@ struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
|
||||
uint32_t elementCount, AttributeArray const& attributes);
|
||||
|
||||
~VulkanVertexBuffer();
|
||||
|
||||
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
|
||||
|
||||
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
|
||||
inline VkVertexInputAttributeDescription const* getAttribDescriptions() {
|
||||
return mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline VkVertexInputBindingDescription const* getBufferDescriptions() {
|
||||
return mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline VkBuffer const* getVkBuffers() const {
|
||||
return mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
|
||||
}
|
||||
|
||||
inline VkDeviceSize const* getOffsets() const {
|
||||
return mInfo->mSoa.data<PipelineInfo::OFFSETS>();
|
||||
}
|
||||
|
||||
private:
|
||||
FixedSizeVulkanResourceManager mResources;
|
||||
struct PipelineInfo {
|
||||
PipelineInfo(size_t size)
|
||||
: mSoa(size /* capacity */) {
|
||||
mSoa.resize(size);
|
||||
}
|
||||
|
||||
// These corresponds to the index of the element in the SoA
|
||||
static constexpr uint8_t ATTRIBUTE_DESCRIPTION = 0;
|
||||
static constexpr uint8_t BUFFER_DESCRIPTION = 1;
|
||||
static constexpr uint8_t VK_BUFFER = 2;
|
||||
static constexpr uint8_t OFFSETS = 3;
|
||||
static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 4;
|
||||
|
||||
utils::StructureOfArrays<
|
||||
VkVertexInputAttributeDescription,
|
||||
VkVertexInputBindingDescription,
|
||||
VkBuffer,
|
||||
VkDeviceSize,
|
||||
int8_t
|
||||
> mSoa;
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
FixedSizeVulkanResourceManager<MAX_VERTEX_BUFFER_COUNT> mResources;
|
||||
};
|
||||
|
||||
struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
@@ -116,7 +206,7 @@ struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
|
||||
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
|
||||
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage);
|
||||
BufferObjectBinding bindingType);
|
||||
|
||||
VulkanBuffer buffer;
|
||||
const BufferObjectBinding bindingType;
|
||||
@@ -146,7 +236,8 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource {
|
||||
VkPrimitiveTopology primitiveTopology;
|
||||
|
||||
private:
|
||||
FixedSizeVulkanResourceManager mResources;
|
||||
// Keep references to the vertex buffer and the index buffer.
|
||||
FixedSizeVulkanResourceManager<2> mResources;
|
||||
};
|
||||
|
||||
struct VulkanFence : public HwFence, VulkanResource {
|
||||
|
||||
@@ -66,8 +66,8 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
|
||||
switch (transition.oldLayout) {
|
||||
case VulkanLayout::UNDEFINED:
|
||||
srcAccessMask = 0;
|
||||
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
|
||||
break;
|
||||
case VulkanLayout::COLOR_ATTACHMENT:
|
||||
srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT
|
||||
@@ -93,8 +93,7 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
break;
|
||||
case VulkanLayout::DEPTH_ATTACHMENT:
|
||||
srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
|
||||
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
break;
|
||||
case VulkanLayout::DEPTH_SAMPLER:
|
||||
@@ -125,7 +124,7 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
break;
|
||||
case VulkanLayout::READ_ONLY:
|
||||
dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
|
||||
break;
|
||||
case VulkanLayout::TRANSFER_SRC:
|
||||
dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
@@ -141,10 +140,10 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||
break;
|
||||
case VulkanLayout::DEPTH_SAMPLER:
|
||||
dstAccessMask
|
||||
= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
||||
| VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||
dstAccessMask =
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||
break;
|
||||
case VulkanLayout::PRESENT:
|
||||
case VulkanLayout::COLOR_ATTACHMENT_RESOLVE:
|
||||
|
||||
@@ -65,6 +65,13 @@ VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, Usa
|
||||
return src;
|
||||
}
|
||||
|
||||
VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding,
|
||||
UsageFlags src) {
|
||||
src.unset(binding);
|
||||
src.unset(MAX_SAMPLER_COUNT + binding);
|
||||
return src;
|
||||
}
|
||||
|
||||
VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator)
|
||||
: mCurrentRasterState(createDefaultRasterState()),
|
||||
mResourceAllocator(allocator),
|
||||
@@ -156,7 +163,7 @@ bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
|
||||
= std::make_unique<VulkanAcquireOnlyResourceManager>(mResourceAllocator);
|
||||
resourceEntry = mDescriptorResources.find(cacheEntry->id);
|
||||
}
|
||||
resourceEntry->second->acquire(&mPipelineBoundResources);
|
||||
resourceEntry->second->acquireAll(&mPipelineBoundResources);
|
||||
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT,
|
||||
@@ -558,12 +565,10 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP
|
||||
return &mPipelineLayouts.emplace(mPipelineRequirements.layout, cacheEntry).first.value();
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindProgram(const VulkanProgram& program) noexcept {
|
||||
const VkShaderModule shaders[2] = { program.bundle.vertex, program.bundle.fragment };
|
||||
for (uint32_t ssi = 0; ssi < SHADER_MODULE_COUNT; ssi++) {
|
||||
mPipelineRequirements.shaders[ssi] = shaders[ssi];
|
||||
}
|
||||
mSpecializationRequirements = program.bundle.specializationInfos;
|
||||
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 {
|
||||
@@ -580,14 +585,21 @@ void VulkanPipelineCache::bindPrimitiveTopology(VkPrimitiveTopology topology) no
|
||||
mPipelineRequirements.topology = topology;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindVertexArray(const VertexArray& varray) noexcept {
|
||||
void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
|
||||
VkVertexInputBindingDescription const* bufferDesc, uint8_t count) {
|
||||
for (size_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
|
||||
mPipelineRequirements.vertexAttributes[i] = varray.attributes[i];
|
||||
mPipelineRequirements.vertexBuffers[i] = varray.buffers[i];
|
||||
if (i < count) {
|
||||
mPipelineRequirements.vertexAttributes[i] = attribDesc[i];
|
||||
mPipelineRequirements.vertexBuffers[i] = bufferDesc[i];
|
||||
} else {
|
||||
mPipelineRequirements.vertexAttributes[i] = {};
|
||||
mPipelineRequirements.vertexBuffers[i] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding(uint32_t bindingIndex) const noexcept {
|
||||
VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding(
|
||||
uint32_t bindingIndex) const noexcept {
|
||||
auto& key = mDescriptorRequirements;
|
||||
return {
|
||||
key.uniformBuffers[bindingIndex],
|
||||
|
||||
@@ -80,8 +80,6 @@ public:
|
||||
// as a vertex assembler configuration. For simplicity it contains fixed-size arrays and does
|
||||
// not store sizes; all unused entries are simply zeroed out.
|
||||
struct VertexArray {
|
||||
VkVertexInputAttributeDescription attributes[VERTEX_ATTRIBUTE_COUNT];
|
||||
VkVertexInputBindingDescription buffers[VERTEX_ATTRIBUTE_COUNT];
|
||||
};
|
||||
|
||||
// The ProgramBundle contains weak references to the compiled vertex and fragment shaders.
|
||||
@@ -93,6 +91,7 @@ public:
|
||||
|
||||
using UsageFlags = utils::bitset128;
|
||||
static UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags stages, UsageFlags src = {});
|
||||
static UsageFlags disableUsageFlags(uint16_t binding, UsageFlags src);
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic warning "-Wpadded"
|
||||
@@ -152,7 +151,7 @@ public:
|
||||
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
|
||||
|
||||
// Each of the following methods are fast and do not make Vulkan calls.
|
||||
void bindProgram(const VulkanProgram& program) noexcept;
|
||||
void bindProgram(VulkanProgram* program) noexcept;
|
||||
void bindRasterState(const RasterState& rasterState) noexcept;
|
||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||
@@ -163,7 +162,8 @@ public:
|
||||
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
||||
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
|
||||
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
|
||||
void bindVertexArray(const VertexArray& varray) noexcept;
|
||||
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
|
||||
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
|
||||
|
||||
// Gets the current UBO at the given slot, useful for push / pop.
|
||||
UniformBufferBinding getUniformBufferBinding(uint32_t bindingIndex) const noexcept;
|
||||
@@ -416,7 +416,7 @@ private:
|
||||
RasterState mCurrentRasterState;
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
DescriptorKey mDescriptorRequirements = {};
|
||||
VkSpecializationInfo* mSpecializationRequirements = {};
|
||||
VkSpecializationInfo const* mSpecializationRequirements = nullptr;
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||
|
||||
#include <backend/DriverEnums.h>// For MAX_VERTEX_BUFFER_COUNT
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <tsl/robin_set.h>
|
||||
@@ -156,13 +155,13 @@ namespace {
|
||||
|
||||
// When the size of the resource set is known to be small, (for example for VulkanRenderPrimitive),
|
||||
// we just use a std::array to back the set.
|
||||
template<std::size_t SIZE>
|
||||
class FixedCapacityResourceSet {
|
||||
private:
|
||||
constexpr static size_t const SIZE = MAX_VERTEX_BUFFER_COUNT;
|
||||
using FixedSizeArray = std::array<VulkanResource*, SIZE>;
|
||||
|
||||
public:
|
||||
using const_iterator = FixedSizeArray::const_iterator;
|
||||
using const_iterator = typename FixedSizeArray::const_iterator;
|
||||
|
||||
inline ~FixedCapacityResourceSet() {
|
||||
clear();
|
||||
@@ -186,7 +185,7 @@ public:
|
||||
}
|
||||
|
||||
inline const_iterator find(VulkanResource* resource) {
|
||||
return std::find(mArray.begin(), mArray.end(), resource);
|
||||
return std::find(begin(), end(), resource);
|
||||
}
|
||||
|
||||
inline void insert(VulkanResource* resource) {
|
||||
@@ -205,6 +204,10 @@ public:
|
||||
mInd = 0;
|
||||
}
|
||||
|
||||
inline size_t size() {
|
||||
return mInd;
|
||||
}
|
||||
|
||||
private:
|
||||
FixedSizeArray mArray{nullptr};
|
||||
size_t mInd = 0;
|
||||
@@ -272,14 +275,21 @@ public:
|
||||
}
|
||||
|
||||
// Transfers ownership from one resource set to another
|
||||
inline void acquire(VulkanResourceManagerImpl<ResourceType, SetType>* srcResources) {
|
||||
template <typename tSetType>
|
||||
inline void acquireAll(VulkanResourceManagerImpl<ResourceType, tSetType>* srcResources) {
|
||||
copyAll(srcResources);
|
||||
srcResources->clear();
|
||||
}
|
||||
|
||||
// Transfers ownership from one resource set to another
|
||||
template <typename tSetType>
|
||||
inline void copyAll(VulkanResourceManagerImpl<ResourceType, tSetType>* srcResources) {
|
||||
LOCK_IF_NEEDED();
|
||||
for (auto iter = srcResources->mResources.begin(); iter != srcResources->mResources.end();
|
||||
iter++) {
|
||||
acquire(*iter);
|
||||
}
|
||||
UNLOCK_IF_NEEDED();
|
||||
srcResources->clear();
|
||||
}
|
||||
|
||||
inline void release(ResourceType* resource) {
|
||||
@@ -323,13 +333,18 @@ private:
|
||||
VulkanResourceAllocator* mAllocator;
|
||||
SetType mResources;
|
||||
std::unique_ptr<utils::Mutex> mMutex;
|
||||
|
||||
template <typename, typename> friend class VulkanResourceManagerImpl;
|
||||
};
|
||||
|
||||
using VulkanAcquireOnlyResourceManager
|
||||
= VulkanResourceManagerImpl<VulkanResource, FastIterationResourceSet>;
|
||||
using VulkanResourceManager = VulkanResourceManagerImpl<VulkanResource, ResourceSet>;
|
||||
using FixedSizeVulkanResourceManager
|
||||
= VulkanResourceManagerImpl<VulkanResource, FixedCapacityResourceSet>;
|
||||
|
||||
template<std::size_t SIZE>
|
||||
using FixedSizeVulkanResourceManager =
|
||||
VulkanResourceManagerImpl<VulkanResource, FixedCapacityResourceSet<SIZE>>;
|
||||
|
||||
using VulkanThreadSafeResourceManager
|
||||
= VulkanResourceManagerImpl<VulkanThreadSafeResource, ThreadSafeResourceSet>;
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
mAllocator(allocator),
|
||||
mStagePool(stagePool),
|
||||
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
|
||||
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
|
||||
mImageReady(VK_NULL_HANDLE),
|
||||
mAcquired(false),
|
||||
mIsFirstRenderPass(true) {
|
||||
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
|
||||
@@ -42,8 +44,13 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
VkSemaphoreCreateInfo const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
};
|
||||
VkResult result = vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, &mImageReady);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore");
|
||||
|
||||
// No need to wait on this semaphore before drawing when in Headless mode.
|
||||
if (!mHeadless) {
|
||||
VkResult result =
|
||||
vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, &mImageReady);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore");
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
@@ -55,7 +62,9 @@ VulkanSwapChain::~VulkanSwapChain() {
|
||||
mCommands->wait();
|
||||
|
||||
mPlatform->destroy(swapChain);
|
||||
vkDestroySemaphore(mPlatform->getDevice(), mImageReady, VKALLOC);
|
||||
if (mImageReady != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(mPlatform->getDevice(), mImageReady, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanSwapChain::update() {
|
||||
@@ -88,8 +97,8 @@ void VulkanSwapChain::present() {
|
||||
.layerCount = 1,
|
||||
};
|
||||
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
|
||||
mCommands->flush();
|
||||
}
|
||||
mCommands->flush();
|
||||
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
|
||||
VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR
|
||||
@@ -109,8 +118,10 @@ void VulkanSwapChain::acquire(bool& resized) {
|
||||
|
||||
// Check if the swapchain should be resized.
|
||||
if ((resized = mPlatform->hasResized(swapChain))) {
|
||||
mCommands->flush();
|
||||
mCommands->wait();
|
||||
if (mFlushAndWaitOnResize) {
|
||||
mCommands->flush();
|
||||
mCommands->wait();
|
||||
}
|
||||
mPlatform->recreate(swapChain);
|
||||
update();
|
||||
}
|
||||
@@ -118,7 +129,9 @@ void VulkanSwapChain::acquire(bool& resized) {
|
||||
VkResult const result = mPlatform->acquire(swapChain, mImageReady, &mCurrentSwapIndex);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR,
|
||||
"Cannot acquire in swapchain.");
|
||||
mCommands->injectDependency(mImageReady);
|
||||
if (mImageReady != VK_NULL_HANDLE) {
|
||||
mCommands->injectDependency(mImageReady);
|
||||
}
|
||||
mAcquired = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ private:
|
||||
VmaAllocator mAllocator;
|
||||
VulkanStagePool& mStagePool;
|
||||
bool const mHeadless;
|
||||
bool const mFlushAndWaitOnResize;
|
||||
|
||||
// We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout
|
||||
// transitions, which are useful here.
|
||||
|
||||
@@ -40,13 +40,14 @@ VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanComm
|
||||
mViewType(ImgUtil::getViewType(target)),
|
||||
mSwizzle({}),
|
||||
mTextureImage(image),
|
||||
mPrimaryViewRange{
|
||||
mFullViewRange{
|
||||
.aspectMask = getImageAspect(),
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
mPrimaryViewRange(mFullViewRange),
|
||||
mStagePool(stagePool),
|
||||
mDevice(device),
|
||||
mAllocator(allocator),
|
||||
@@ -60,9 +61,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
|
||||
VulkanResource(
|
||||
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
|
||||
// Vulkan does not support 24-bit depth, use the official fallback format.
|
||||
mVkFormat(tformat == TextureFormat::DEPTH24 ? context.getDepthFormat()
|
||||
: backend::getVkFormat(tformat)),
|
||||
mVkFormat(backend::getVkFormat(tformat)),
|
||||
mViewType(ImgUtil::getViewType(target)),
|
||||
mSwizzle(swizzle),
|
||||
mStagePool(stagePool),
|
||||
@@ -145,7 +144,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
// any kind of attachment (color or depth).
|
||||
const auto& limits = context.getPhysicalDeviceLimits();
|
||||
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
|
||||
samples = reduceSampleCount(samples, isDepthFormat(mVkFormat)
|
||||
samples = reduceSampleCount(samples, isVkDepthFormat(mVkFormat)
|
||||
? limits.sampledImageDepthSampleCounts
|
||||
: limits.sampledImageColorSampleCounts);
|
||||
}
|
||||
@@ -197,24 +196,31 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
error = vkBindImageMemory(mDevice, mTextureImage, mTextureImageMemory, 0);
|
||||
ASSERT_POSTCONDITION(!error, "Unable to bind image.");
|
||||
|
||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||
mPrimaryViewRange.aspectMask = getImageAspect();
|
||||
mPrimaryViewRange.baseMipLevel = 0;
|
||||
mPrimaryViewRange.levelCount = levels;
|
||||
mPrimaryViewRange.baseArrayLayer = 0;
|
||||
uint32_t layerCount = 0;
|
||||
if (target == SamplerType::SAMPLER_CUBEMAP) {
|
||||
mPrimaryViewRange.layerCount = 6;
|
||||
layerCount = 6;
|
||||
} else if (target == SamplerType::SAMPLER_CUBEMAP_ARRAY) {
|
||||
mPrimaryViewRange.layerCount = depth * 6;
|
||||
layerCount = depth * 6;
|
||||
} else if (target == SamplerType::SAMPLER_2D_ARRAY) {
|
||||
mPrimaryViewRange.layerCount = depth;
|
||||
layerCount = depth;
|
||||
} else if (target == SamplerType::SAMPLER_3D) {
|
||||
mPrimaryViewRange.layerCount = 1;
|
||||
layerCount = 1;
|
||||
} else {
|
||||
mPrimaryViewRange.layerCount = 1;
|
||||
layerCount = 1;
|
||||
}
|
||||
|
||||
// Go ahead and create the primary image view, no need to do it lazily.
|
||||
mFullViewRange = {
|
||||
.aspectMask = getImageAspect(),
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = levels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = layerCount,
|
||||
};
|
||||
|
||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||
mPrimaryViewRange = mFullViewRange;
|
||||
|
||||
// Go ahead and create the primary image view.
|
||||
getImageView(mPrimaryViewRange, mViewType, mSwizzle);
|
||||
|
||||
// Transition the layout of each image slice that might be used as a render target.
|
||||
@@ -223,14 +229,10 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
if (imageInfo.usage
|
||||
& (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
|
||||
| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
|
||||
const uint32_t layers = mPrimaryViewRange.layerCount;
|
||||
VkImageSubresourceRange range = { getImageAspect(), 0, levels, 0, layers };
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(this);
|
||||
|
||||
transitionLayout(cmdbuf, range, ImgUtil::getDefaultLayout(imageInfo.usage));
|
||||
transitionLayout(cmdbuf, mFullViewRange, ImgUtil::getDefaultLayout(imageInfo.usage));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +272,8 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
return;
|
||||
}
|
||||
|
||||
assert_invariant(hostData->size > 0 && "Data is empty");
|
||||
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
void* mapped = nullptr;
|
||||
VulkanStage const* stage = mStagePool.acquireStage(hostData->size);
|
||||
@@ -318,11 +322,11 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
}
|
||||
|
||||
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
|
||||
VulkanLayout nextLayout = getLayout(0, miplevel);
|
||||
VulkanLayout nextLayout = getLayout(transitionRange.baseArrayLayer, miplevel);
|
||||
VkImageLayout const newVkLayout = ImgUtil::getVkLayout(newLayout);
|
||||
|
||||
if (nextLayout == VulkanLayout::UNDEFINED) {
|
||||
nextLayout = VulkanLayout::READ_WRITE;
|
||||
nextLayout = ImgUtil::getDefaultLayout(this->usage);
|
||||
}
|
||||
|
||||
transitionLayout(cmdbuf, transitionRange, newLayout);
|
||||
@@ -360,10 +364,10 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
||||
.dstOffsets = { rect[0], rect[1] }
|
||||
}};
|
||||
|
||||
const VkImageSubresourceRange range = { aspect, miplevel, 1, 0, 1 };
|
||||
const VkImageSubresourceRange range = { aspect, miplevel, 1, layer, 1 };
|
||||
|
||||
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
|
||||
VulkanLayout const oldLayout = getLayout(0, miplevel);
|
||||
VulkanLayout const oldLayout = getLayout(layer, miplevel);
|
||||
transitionLayout(cmdbuf, range, newLayout);
|
||||
|
||||
vkCmdBlitImage(cmdbuf, stage->image, ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
@@ -386,9 +390,14 @@ VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
|
||||
return getImageView(range, type, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle) {
|
||||
auto iter = mCachedImageViews.find(range);
|
||||
ImageViewKey const key {range, viewType, swizzle};
|
||||
auto iter = mCachedImageViews.find(key);
|
||||
if (iter != mCachedImageViews.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
@@ -404,7 +413,7 @@ VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageVi
|
||||
};
|
||||
VkImageView imageView;
|
||||
vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView);
|
||||
mCachedImageViews.emplace(range, imageView);
|
||||
mCachedImageViews.emplace(key, imageView);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
@@ -415,22 +424,72 @@ VkImageAspectFlags VulkanTexture::getImageAspect() const {
|
||||
|
||||
void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubresourceRange& range,
|
||||
VulkanLayout newLayout) {
|
||||
VulkanLayout oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
|
||||
utils::slog.i << "transition layout of " << mTextureImage << ",layer=" << range.baseArrayLayer
|
||||
<< ",level=" << range.baseMipLevel << " from=" << oldLayout << " to=" << newLayout
|
||||
VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
|
||||
|
||||
uint32_t const firstLayer = range.baseArrayLayer;
|
||||
uint32_t const lastLayer = firstLayer + range.layerCount;
|
||||
uint32_t const firstLevel = range.baseMipLevel;
|
||||
uint32_t const lastLevel = firstLevel + range.levelCount;
|
||||
|
||||
// If we are transitioning more than one layer/level (slice), we need to know whether they are
|
||||
// all of the same layer. If not, we need to transition slice-by-slice. Otherwise it would
|
||||
// trigger the validation layer saying that the `oldLayout` provided is incorrect.
|
||||
// TODO: transition by multiple slices with more sophiscated range finding.
|
||||
bool transitionSliceBySlice = false;
|
||||
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
|
||||
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
|
||||
if (oldLayout != getLayout(i, j)) {
|
||||
transitionSliceBySlice = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
|
||||
utils::slog.d << "transition texture=" << mTextureImage
|
||||
<< " (" << range.baseArrayLayer
|
||||
<< "," << range.baseMipLevel << ")"
|
||||
<< " count=(" << range.layerCount
|
||||
<< "," << range.levelCount << ")"
|
||||
<< " from=" << oldLayout << " to=" << newLayout
|
||||
<< " format=" << mVkFormat
|
||||
<< " depth=" << isDepthFormat(mVkFormat) << utils::io::endl;
|
||||
#endif
|
||||
<< " depth=" << isVkDepthFormat(mVkFormat)
|
||||
<< " slice-by-slice=" << transitionSliceBySlice
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
ImgUtil::transitionLayout(cmdbuf, {
|
||||
if (transitionSliceBySlice) {
|
||||
for (uint32_t i = firstLayer; i < lastLayer; ++i) {
|
||||
for (uint32_t j = firstLevel; j < lastLevel; ++j) {
|
||||
VulkanLayout const layout = getLayout(i, j);
|
||||
ImgUtil::transitionLayout(cmdbuf, {
|
||||
.image = mTextureImage,
|
||||
.oldLayout = layout,
|
||||
.newLayout = newLayout,
|
||||
.subresources = {
|
||||
.aspectMask = range.aspectMask,
|
||||
.baseMipLevel = j,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = i,
|
||||
.layerCount = 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ImgUtil::transitionLayout(cmdbuf, {
|
||||
.image = mTextureImage,
|
||||
.oldLayout = oldLayout,
|
||||
.newLayout = newLayout,
|
||||
.subresources = range,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setLayout(range, newLayout);
|
||||
}
|
||||
|
||||
void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout) {
|
||||
uint32_t const firstLayer = range.baseArrayLayer;
|
||||
uint32_t const lastLayer = firstLayer + range.layerCount;
|
||||
uint32_t const firstLevel = range.baseMipLevel;
|
||||
@@ -465,17 +524,33 @@ VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
void VulkanTexture::print() const {
|
||||
const uint32_t firstLayer = 0;
|
||||
const uint32_t lastLayer = firstLayer + mPrimaryViewRange.layerCount;
|
||||
const uint32_t firstLevel = 0;
|
||||
const uint32_t lastLevel = firstLevel + mPrimaryViewRange.levelCount;
|
||||
uint32_t const firstLayer = 0;
|
||||
uint32_t const lastLayer = firstLayer + mFullViewRange.layerCount;
|
||||
uint32_t const firstLevel = 0;
|
||||
uint32_t const lastLevel = firstLevel + mFullViewRange.levelCount;
|
||||
|
||||
for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) {
|
||||
for (uint32_t level = firstLevel; level < lastLevel; ++level) {
|
||||
bool primary =
|
||||
layer >= mPrimaryViewRange.baseArrayLayer &&
|
||||
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
|
||||
level >= mPrimaryViewRange.baseMipLevel &&
|
||||
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
|
||||
utils::slog.d << "[" << mTextureImage << "]: (" << layer << "," << level
|
||||
<< ")=" << getLayout(layer, level) << utils::io::endl;
|
||||
<< ")=" << getLayout(layer, level)
|
||||
<< " primary=" << primary
|
||||
<< utils::io::endl;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto view: mCachedImageViews) {
|
||||
auto& range = view.first.range;
|
||||
utils::slog.d << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
|
||||
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
|
||||
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
|
||||
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type
|
||||
<< utils::io::endl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#include <utils/RangeMap.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
@@ -48,12 +50,18 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
|
||||
|
||||
// Returns the primary image view, which is used for shader sampling.
|
||||
VkImageView getPrimaryImageView() const { return mCachedImageViews.at(mPrimaryViewRange); }
|
||||
VkImageView getPrimaryImageView() {
|
||||
return getImageView(mPrimaryViewRange, mViewType, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageViewType getViewType() const { return mViewType; }
|
||||
|
||||
// Sets the min/max range of miplevels in the primary image view.
|
||||
void setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel);
|
||||
|
||||
VkImageSubresourceRange getPrimaryRange() const { return mPrimaryViewRange; }
|
||||
VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; }
|
||||
|
||||
VkImageSubresourceRange getFullViewRange() const { return mFullViewRange; }
|
||||
|
||||
VulkanLayout getPrimaryImageLayout() const {
|
||||
return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel);
|
||||
@@ -64,6 +72,12 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
// and the identity swizzle.
|
||||
VkImageView getAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// This is a workaround for the first few frames where we're waiting for the texture to actually
|
||||
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
|
||||
// imageView needs to be of the right type. Hence, we provide an option to indicate the
|
||||
// view type. Swizzle option does not matter in this case.
|
||||
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
|
||||
|
||||
VkFormat getVkFormat() const { return mVkFormat; }
|
||||
VkImage getVkImage() const { return mTextureImage; }
|
||||
|
||||
@@ -84,11 +98,37 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
// For now this always returns either DEPTH or COLOR.
|
||||
VkImageAspectFlags getImageAspect() const;
|
||||
|
||||
// For implicit transition like the end of a render pass, we need to be able to set the layout
|
||||
// manually (outside of calls to transitionLayout).
|
||||
void setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
void print() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
struct ImageViewKey {
|
||||
VkImageSubresourceRange range; // 4 * 5 bytes
|
||||
VkImageViewType type; // 4 bytes
|
||||
VkComponentMapping swizzle; // 4 * 4 bytes
|
||||
|
||||
bool operator==(ImageViewKey const& k2) const {
|
||||
auto const& k1 = *this;
|
||||
return k1.range.aspectMask == k2.range.aspectMask
|
||||
&& k1.range.baseMipLevel == k2.range.baseMipLevel
|
||||
&& k1.range.levelCount == k2.range.levelCount
|
||||
&& k1.range.baseArrayLayer == k2.range.baseArrayLayer
|
||||
&& k1.range.layerCount == k2.range.layerCount && k1.type == k2.type
|
||||
&& k1.swizzle.r == k2.swizzle.r && k1.swizzle.g == k2.swizzle.g
|
||||
&& k1.swizzle.b == k2.swizzle.b && k1.swizzle.a == k2.swizzle.a;
|
||||
}
|
||||
};
|
||||
// No implicit padding allowed due to it being a hash key.
|
||||
static_assert(sizeof(ImageViewKey) == 40);
|
||||
|
||||
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
||||
|
||||
// Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and
|
||||
// swizzle (or not).
|
||||
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
@@ -108,11 +148,13 @@ private:
|
||||
// Track the image layout of each subresource using a sparse range map.
|
||||
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
|
||||
|
||||
VkImageSubresourceRange mFullViewRange;
|
||||
|
||||
// Track the range of subresources that define the "primary" image view, which is the special
|
||||
// image view that gets bound to an actual texture sampler.
|
||||
VkImageSubresourceRange mPrimaryViewRange;
|
||||
|
||||
std::map<VkImageSubresourceRange, VkImageView> mCachedImageViews;
|
||||
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
||||
VulkanStagePool& mStagePool;
|
||||
VkDevice mDevice;
|
||||
VmaAllocator mAllocator;
|
||||
|
||||
@@ -121,8 +121,8 @@ VkFormat getVkFormat(TextureFormat format) {
|
||||
case TextureFormat::RGB8UI: return VK_FORMAT_R8G8B8A8_UINT;
|
||||
case TextureFormat::RGB8I: return VK_FORMAT_R8G8B8A8_SINT;
|
||||
|
||||
case TextureFormat::DEPTH24:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
// A 32-bit format but 8 bits are unused.
|
||||
case TextureFormat::DEPTH24: return VK_FORMAT_X8_D24_UNORM_PACK32;
|
||||
|
||||
// 32 bits per element.
|
||||
case TextureFormat::R32F: return VK_FORMAT_R32_SFLOAT;
|
||||
@@ -638,18 +638,12 @@ VkImageAspectFlags getImageAspect(VkFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
bool isDepthFormat(VkFormat format) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
case VK_FORMAT_X8_D24_UNORM_PACK32:
|
||||
case VK_FORMAT_D16_UNORM_S8_UINT:
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
case VK_FORMAT_D32_SFLOAT:
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
bool isVkDepthFormat(VkFormat format) {
|
||||
return (getImageAspect(format) & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
|
||||
}
|
||||
|
||||
bool isVkStencilFormat(VkFormat format) {
|
||||
return (getImageAspect(format) & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
|
||||
}
|
||||
|
||||
static uint32_t mostSignificantBit(uint32_t x) { return 1ul << (31ul - utils::clz(x)); }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user