Compare commits
218
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea9fdbeb5c | ||
|
|
5fd7a4e153 | ||
|
|
20ff230b92 | ||
|
|
44ff79ad34 | ||
|
|
102d2db008 | ||
|
|
5c9039e650 | ||
|
|
1203c24f06 | ||
|
|
9704d27aeb | ||
|
|
c6f2c3fc1c | ||
|
|
2faf868341 | ||
|
|
81f6260843 | ||
|
|
1a9063d53a | ||
|
|
6062b3c8c6 | ||
|
|
d9186c44ba | ||
|
|
6d06e00934 | ||
|
|
b4021a1fbc | ||
|
|
2c9559f9b8 | ||
|
|
e2da13f817 | ||
|
|
addae314b4 | ||
|
|
5e3c51822d | ||
|
|
9d817a0cc9 | ||
|
|
95ecc3d1e4 | ||
|
|
cabaa4323e | ||
|
|
d6b2ec49b0 | ||
|
|
93004aa5bc | ||
|
|
27fa8ab46d | ||
|
|
614990f413 | ||
|
|
da058028fd | ||
|
|
55c75453df | ||
|
|
f5eff10532 | ||
|
|
56a0364534 | ||
|
|
c8aae3cdaa | ||
|
|
4f94c575c9 | ||
|
|
1a29f020c9 | ||
|
|
d5847a3c6a | ||
|
|
3dbae482d3 | ||
|
|
27572e3be6 | ||
|
|
dedf276e26 | ||
|
|
111ad96134 | ||
|
|
2a67152dda | ||
|
|
2f62978e4c | ||
|
|
fdb90f2f49 | ||
|
|
5418b8c4cc | ||
|
|
724cab3623 | ||
|
|
d9faea264a | ||
|
|
6116c2f9eb | ||
|
|
95fdba0c5d | ||
|
|
9c56517370 | ||
|
|
08f06f4298 | ||
|
|
b1794a82ca | ||
|
|
56bd2871ec | ||
|
|
5a9c47bcb5 | ||
|
|
51c2fa2728 | ||
|
|
334c71de52 | ||
|
|
001098098b | ||
|
|
35a00b1d84 | ||
|
|
b517a45213 | ||
|
|
f8057b9a5b | ||
|
|
d9ee526a72 | ||
|
|
164d7507bb | ||
|
|
b007e9137e | ||
|
|
06bae26e1b | ||
|
|
1be3e13427 | ||
|
|
e7c67d1adb | ||
|
|
9b67d80961 | ||
|
|
b066678a37 | ||
|
|
b10d5c58aa | ||
|
|
000bff5ce1 | ||
|
|
47d45a64fa | ||
|
|
2bd48f58ff | ||
|
|
f2f4f556b5 | ||
|
|
5aa15f061b | ||
|
|
63db4f0bf0 | ||
|
|
7797f5af38 | ||
|
|
f9def09d17 | ||
|
|
494934f34e | ||
|
|
caae42fdf6 | ||
|
|
8f6c4841cb | ||
|
|
5d0c06e3f2 | ||
|
|
7d31a7f7ea | ||
|
|
4f4a439f8b | ||
|
|
2306b56ea7 | ||
|
|
eebb40d30d | ||
|
|
766e4f2ec9 | ||
|
|
b0a584c915 | ||
|
|
d5bab43ceb | ||
|
|
485ae1b324 | ||
|
|
75e8961109 | ||
|
|
0e9b2eda0a | ||
|
|
a900bc69fb | ||
|
|
edbfefda0a | ||
|
|
1a0b5ddc14 | ||
|
|
e8bed52b3f | ||
|
|
06e8b1d689 | ||
|
|
9c351b28ab | ||
|
|
cbc3bb3326 | ||
|
|
0eb851ff8c | ||
|
|
038f07cb34 | ||
|
|
62adb234d1 | ||
|
|
53d8d2a41f | ||
|
|
974a69a273 | ||
|
|
7d694ee85e | ||
|
|
46e6664c3d | ||
|
|
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)
|
||||
|
||||
|
||||
+39
-1
@@ -41,6 +41,10 @@ option(FILAMENT_LINUX_IS_MOBILE "Treat Linux as Mobile" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_ASAN_UBSAN "Enable Address and Undefined Behavior Sanitizers" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_TSAN "Enable Thread Sanitizer" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
|
||||
|
||||
set(FILAMENT_NDK_VERSION "" CACHE STRING
|
||||
"Android NDK version or version prefix to be used when building for Android."
|
||||
)
|
||||
@@ -65,6 +69,9 @@ set(FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB "8" CACHE STRING
|
||||
"Size of the Metal handle arena, default 8."
|
||||
)
|
||||
|
||||
# Enable exceptions by default in spirv-cross.
|
||||
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
|
||||
|
||||
# ==================================================================================================
|
||||
# CMake policies
|
||||
# ==================================================================================================
|
||||
@@ -225,6 +232,21 @@ if (WIN32)
|
||||
# we don't need them on CI.
|
||||
string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" ${LinkerFlag} ${${LinkerFlag}})
|
||||
endforeach()
|
||||
|
||||
# We turn off compile-time optimizations for CI, as options that speed up the compile-time
|
||||
# (e.g. /MP) might increase memory usage, leading to instabilities on limited CI machines.
|
||||
option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" OFF)
|
||||
else()
|
||||
option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" ON)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
if (FILAMENT_SHORTEN_MSVC_COMPILATION)
|
||||
# enable multi-processor compilation
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
# disable run-time STL checks to improve tools (e.g. matc) performance
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_ITERATOR_DEBUG_LEVEL=0")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -337,6 +359,7 @@ endif()
|
||||
|
||||
if (CYGWIN)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
|
||||
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
@@ -373,6 +396,7 @@ endif()
|
||||
# saved by -fno-exception and 10 KiB saved by -fno-rtti).
|
||||
if (ANDROID OR IOS OR WEBGL)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-exceptions -fno-rtti")
|
||||
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON)
|
||||
|
||||
if (ANDROID OR WEBGL)
|
||||
# Omitting unwind info prevents the generation of readable stack traces in crash reports on iOS
|
||||
@@ -384,6 +408,7 @@ endif()
|
||||
# std::visit, which is not supported on iOS 11.0 when exceptions are enabled.
|
||||
if (IOS)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-exceptions")
|
||||
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON)
|
||||
endif()
|
||||
|
||||
# With WebGL, we disable RTTI even for debug builds because we pass emscripten::val back and forth
|
||||
@@ -403,7 +428,13 @@ endif()
|
||||
if (FILAMENT_ENABLE_ASAN_UBSAN)
|
||||
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=address -fsanitize=undefined")
|
||||
endif()
|
||||
|
||||
if (FILAMENT_ENABLE_TSAN)
|
||||
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=thread")
|
||||
endif()
|
||||
if (ANDROID)
|
||||
# keep STL debug infos (mimics what the NDK does)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-limit-debug-info")
|
||||
endif()
|
||||
if (NOT MSVC AND NOT WEBGL)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstack-protector")
|
||||
endif()
|
||||
@@ -523,6 +554,11 @@ if (FILAMENT_SUPPORTS_METAL)
|
||||
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a metal)
|
||||
endif()
|
||||
|
||||
# Disable ESSL 1.0 code generation.
|
||||
if (NOT FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
set(MATC_API_FLAGS ${MATC_API_FLAGS} -1)
|
||||
endif()
|
||||
|
||||
# Enable debug info (preserves names in SPIR-V)
|
||||
if (FILAMENT_ENABLE_MATDBG)
|
||||
set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -d)
|
||||
@@ -730,6 +766,8 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
add_subdirectory(${LIBRARIES}/bluevk)
|
||||
add_subdirectory(${EXTERNAL}/vkmemalloc/tnt)
|
||||
set(SPIRV_HEADERS_SKIP_EXAMPLES ON)
|
||||
add_subdirectory(${EXTERNAL}/spirv-headers)
|
||||
endif()
|
||||
|
||||
set(FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples)
|
||||
|
||||
@@ -7,3 +7,5 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Metal: fix some shader artifacts by disabling fast math optimizations.
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.43.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.49.3'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.49.3'
|
||||
```
|
||||
|
||||
### 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,72 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.50.0
|
||||
- engine: TAA now supports 4x upscaling [BETA] [⚠️ **New Material Version**]
|
||||
|
||||
## v1.49.3
|
||||
|
||||
- matc: Generate stereo variants for FL0 materials [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.49.2
|
||||
|
||||
|
||||
## v1.49.1
|
||||
|
||||
|
||||
## v1.49.0
|
||||
|
||||
- matc: Fix ESSL 1.0 codegen when using external samplers [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.48.0
|
||||
|
||||
- matc: New option `-1` to disable generation of ESSL 1.0 code in Feature Level 0 materials
|
||||
- matc: Support optimizations for ESSL 1.0 code [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.47.0
|
||||
|
||||
- engine: Support up to 4 side-by-side stereoscopic eyes, configurable at Engine creation time. See
|
||||
`Engine::Config::stereoscopicEyeCount`. [⚠️ **Recompile Materials**]
|
||||
|
||||
## v1.46.0
|
||||
|
||||
- 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
|
||||
- engine: Fix critical GLES 2.0 bugs
|
||||
- engine: Add `FILAMENT_ENABLE_FEATURE_LEVEL_0` build-time option optionally allow building Filament
|
||||
without FL0 support.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -81,14 +81,14 @@ buildscript {
|
||||
ext.versions = [
|
||||
'jdk': 17,
|
||||
'minSdk': 19,
|
||||
'targetSdk': 33,
|
||||
'compileSdk': 33,
|
||||
'kotlin': '1.9.0',
|
||||
'kotlin_coroutines': '1.7.2',
|
||||
'targetSdk': 34,
|
||||
'compileSdk': 34,
|
||||
'kotlin': '1.9.21',
|
||||
'kotlin_coroutines': '1.7.3',
|
||||
'buildTools': '34.0.0',
|
||||
'ndk': '25.1.8937393',
|
||||
'androidx_core': '1.10.1',
|
||||
'androidx_annotations': '1.6.0'
|
||||
'ndk': '26.1.10909125',
|
||||
'androidx_core': '1.12.0',
|
||||
'androidx_annotations': '1.7.0'
|
||||
]
|
||||
|
||||
ext.deps = [
|
||||
@@ -104,7 +104,7 @@ buildscript {
|
||||
]
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -391,6 +384,13 @@ Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass,
|
||||
engine->flushAndWait();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
engine->flush();
|
||||
}
|
||||
|
||||
// Managers...
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
@@ -435,6 +435,13 @@ Java_com_google_android_filament_Engine_nIsAutomaticInstancingEnabled(JNIEnv*, j
|
||||
return (jboolean)engine->isAutomaticInstancingEnabled();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Engine_nGetMaxStereoscopicEyes(JNIEnv*, jclass, jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jlong) engine->getMaxStereoscopicEyes();
|
||||
}
|
||||
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Engine_nGetSupportedFeatureLevel(JNIEnv *, jclass,
|
||||
jlong nativeEngine) {
|
||||
@@ -454,4 +461,57 @@ 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, jlong stereoscopicEyeCount) {
|
||||
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,
|
||||
.stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount,
|
||||
};
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ Java_com_google_android_filament_EntityManager_nCreateArray(JNIEnv* env, jclass,
|
||||
// (which it is), but still.
|
||||
em->create((size_t) n, reinterpret_cast<Entity *>(entities));
|
||||
|
||||
env->ReleaseIntArrayElements(entities_, entities, 0);
|
||||
env->ReleaseIntArrayElements(entities_, entities, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <filament/Material.h>
|
||||
|
||||
#include "common/NioUtils.h"
|
||||
#include "common/CallbackUtils.h"
|
||||
|
||||
using namespace filament;
|
||||
|
||||
@@ -105,6 +106,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,
|
||||
@@ -255,3 +272,17 @@ Java_com_google_android_filament_Material_nHasParameter(JNIEnv* env, jclass,
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
return (jboolean) hasParameter;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Material_nCompile(JNIEnv *env, jclass clazz,
|
||||
jlong nativeMaterial, jint priority, jint variants, jobject handler, jobject runnable) {
|
||||
Material* material = (Material*) nativeMaterial;
|
||||
JniCallback* jniCallback = JniCallback::make(env, handler, runnable);
|
||||
material->compile(
|
||||
(Material::CompilerPriorityQueue) priority,
|
||||
(UserVariantFilterBit) variants,
|
||||
jniCallback->getHandler(), [jniCallback](Material*){
|
||||
JniCallback::postToJavaAndDestroy(jniCallback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ Java_com_google_android_filament_MaterialInstance_nSetIntParameterArray(JNIEnv *
|
||||
break;
|
||||
}
|
||||
|
||||
env->ReleaseIntArrayElements(v_, v, 0);
|
||||
env->ReleaseIntArrayElements(v_, v, JNI_ABORT);
|
||||
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -91,3 +98,22 @@ Java_com_google_android_filament_Scene_nHasEntity(JNIEnv *env, jclass type, jlon
|
||||
Entity entity = Entity::import(entityId);
|
||||
return (jboolean) scene->hasEntity(entity);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Scene_nGetEntities(JNIEnv *env, jclass ,
|
||||
jlong nativeScene, jintArray outArray, jint length) {
|
||||
Scene const* const scene = (Scene*) nativeScene;
|
||||
if (length < scene->getEntityCount()) {
|
||||
// should not happen because we already checked on the java side
|
||||
return JNI_FALSE;
|
||||
}
|
||||
jint *out = (jint *) env->GetIntArrayElements(outArray, nullptr);
|
||||
scene->forEach([out, length, i = 0](Entity entity)mutable {
|
||||
if (i < length) { // this is just paranoia here
|
||||
out[i++] = (jint) entity.getId();
|
||||
}
|
||||
});
|
||||
env->ReleaseIntArrayElements(outArray, (jint*) out, 0);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -481,6 +480,17 @@ Java_com_google_android_filament_View_nIsStencilBufferEnabled(JNIEnv *, jclass,
|
||||
return view->isStencilBufferEnabled();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetStereoscopicOptions(JNIEnv *, jclass, jlong nativeView,
|
||||
jboolean enabled) {
|
||||
View* view = (View*) nativeView;
|
||||
View::StereoscopicOptions options {
|
||||
.enabled = (bool) enabled
|
||||
};
|
||||
view->setStereoscopicOptions(options);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetGuardBandOptions(JNIEnv *, jclass,
|
||||
|
||||
@@ -111,6 +111,8 @@ public class Engine {
|
||||
|
||||
private long mNativeObject;
|
||||
|
||||
private Config mConfig;
|
||||
|
||||
@NonNull private final TransformManager mTransformManager;
|
||||
@NonNull private final LightManager mLightManager;
|
||||
@NonNull private final RenderableManager mRenderableManager;
|
||||
@@ -150,16 +152,219 @@ public class Engine {
|
||||
FEATURE_LEVEL_0,
|
||||
/** OpenGL ES 3.0 features (default) */
|
||||
FEATURE_LEVEL_1,
|
||||
/** OpenGL ES 3.1 features + 16 textures units + cubemap arrays */
|
||||
FEATURE_LEVEL_2,
|
||||
/** OpenGL ES 3.1 features + 31 textures units + cubemap arrays */
|
||||
FEATURE_LEVEL_2
|
||||
FEATURE_LEVEL_3,
|
||||
};
|
||||
|
||||
private Engine(long nativeEngine) {
|
||||
/**
|
||||
* Constructs <code>Engine</code> objects using a builder pattern.
|
||||
*/
|
||||
public static class Builder {
|
||||
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
|
||||
private final BuilderFinalizer mFinalizer;
|
||||
private final long mNativeBuilder;
|
||||
private Config mConfig;
|
||||
|
||||
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) {
|
||||
mConfig = config;
|
||||
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
|
||||
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
|
||||
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
|
||||
config.jobSystemThreadCount, config.stereoscopicEyeCount);
|
||||
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, mConfig);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
* between 1 and Engine#getMaxStereoscopicEyes() (inclusive).
|
||||
*
|
||||
* @see View#setStereoscopicOptions
|
||||
* @see Engine#getMaxStereoscopicEyes
|
||||
*/
|
||||
public long stereoscopicEyeCount = 2;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine, Config config) {
|
||||
mNativeObject = nativeEngine;
|
||||
mTransformManager = new TransformManager(nGetTransformManager(nativeEngine));
|
||||
mLightManager = new LightManager(nGetLightManager(nativeEngine));
|
||||
mRenderableManager = new RenderableManager(nGetRenderableManager(nativeEngine));
|
||||
mEntityManager = new EntityManager(nGetEntityManager(nativeEngine));
|
||||
mConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,9 +382,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 +402,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 +426,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 +495,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
|
||||
*/
|
||||
@@ -352,6 +557,37 @@ public class Engine {
|
||||
return nIsAutomaticInstancingEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configuration settings of this {@link Engine}.
|
||||
*
|
||||
* This method returns the configuration object that was supplied to the Engine's {@link
|
||||
* Builder#config} method during the creation of this Engine. If the {@link Builder::config}
|
||||
* method was not explicitly called (or called with null), this method returns the default
|
||||
* configuration settings.
|
||||
*
|
||||
* @return a {@link Config} object with this Engine's configuration
|
||||
* @see Builder#config
|
||||
*/
|
||||
@NonNull
|
||||
public Config getConfig() {
|
||||
if (mConfig == null) {
|
||||
mConfig = new Config();
|
||||
}
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
|
||||
* eyes rendered is set at Engine creation time with the {@link
|
||||
* Engine#Config#stereoscopicEyeCount} setting.
|
||||
*
|
||||
* @return the max number of stereoscopic eyes supported
|
||||
* @see Engine#Config#stereoscopicEyeCount
|
||||
*/
|
||||
public long getMaxStereoscopicEyes() {
|
||||
return nGetMaxStereoscopicEyes(getNativeObject());
|
||||
}
|
||||
|
||||
|
||||
// SwapChain
|
||||
|
||||
@@ -888,6 +1124,18 @@ public class Engine {
|
||||
nFlushAndWait(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) but does not wait
|
||||
* for commands to be either executed or the hardware finished.
|
||||
*
|
||||
* <p>This is typically used after creating a lot of objects to start draining the command
|
||||
* queue which has a limited size.</p>
|
||||
*/
|
||||
public void flush() {
|
||||
nFlush(getNativeObject());
|
||||
}
|
||||
|
||||
|
||||
@UsedByReflection("TextureHelper.java")
|
||||
public long getNativeObject() {
|
||||
if (mNativeObject == 0) {
|
||||
@@ -914,7 +1162,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);
|
||||
@@ -961,6 +1208,7 @@ public class Engine {
|
||||
private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain);
|
||||
private static native void nDestroyEntity(long nativeEngine, int entity);
|
||||
private static native void nFlushAndWait(long nativeEngine);
|
||||
private static native void nFlush(long nativeEngine);
|
||||
private static native long nGetTransformManager(long nativeEngine);
|
||||
private static native long nGetLightManager(long nativeEngine);
|
||||
private static native long nGetRenderableManager(long nativeEngine);
|
||||
@@ -968,7 +1216,19 @@ public class Engine {
|
||||
private static native long nGetEntityManager(long nativeEngine);
|
||||
private static native void nSetAutomaticInstancingEnabled(long nativeEngine, boolean enable);
|
||||
private static native boolean nIsAutomaticInstancingEnabled(long nativeEngine);
|
||||
private static native long nGetMaxStereoscopicEyes(long nativeEngine);
|
||||
private static native int nGetSupportedFeatureLevel(long nativeEngine);
|
||||
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
|
||||
private static native int nGetActiveFeatureLevel(long nativeEngine);
|
||||
|
||||
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,
|
||||
long stereoscopicEyeCount);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ package com.google.android.filament;
|
||||
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
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 +48,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 +185,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
|
||||
*
|
||||
@@ -223,6 +239,31 @@ public class Material {
|
||||
FRONT_AND_BACK
|
||||
}
|
||||
|
||||
public enum CompilerPriorityQueue {
|
||||
HIGH,
|
||||
LOW
|
||||
}
|
||||
|
||||
public static class UserVariantFilterBit {
|
||||
/** Directional lighting */
|
||||
public static int DIRECTIONAL_LIGHTING = 0x01;
|
||||
/** Dynamic lighting */
|
||||
public static int DYNAMIC_LIGHTING = 0x02;
|
||||
/** Shadow receiver */
|
||||
public static int SHADOW_RECEIVER = 0x04;
|
||||
/** Skinning */
|
||||
public static int SKINNING = 0x08;
|
||||
/** Fog */
|
||||
public static int FOG = 0x10;
|
||||
/** Variance shadow maps */
|
||||
public static int VSM = 0x20;
|
||||
/** Screen-space reflections */
|
||||
public static int SSR = 0x40;
|
||||
/** Instanced stereo rendering */
|
||||
public static int STE = 0x80;
|
||||
public static int ALL = 0xFF;
|
||||
}
|
||||
|
||||
@UsedByNative("Material.cpp")
|
||||
public static class Parameter {
|
||||
private static final Type[] sTypeValues = Type.values();
|
||||
@@ -337,6 +378,55 @@ public class Material {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronously ensures that a subset of this Material's variants are compiled. After issuing
|
||||
* several compile() calls in a row, it is recommended to call {@link Engine#flush}
|
||||
* such that the backend can start the compilation work as soon as possible.
|
||||
* The provided callback is guaranteed to be called on the main thread after all specified
|
||||
* variants of the material are compiled. This can take hundreds of milliseconds.
|
||||
*<p>
|
||||
* If all the material's variants are already compiled, the callback will be scheduled as
|
||||
* soon as possible, but this might take a few dozen millisecond, corresponding to how
|
||||
* many previous frames are enqueued in the backend. This also varies by backend. Therefore,
|
||||
* it is recommended to only call this method once per material shortly after creation.
|
||||
*</p>
|
||||
*<p>
|
||||
* If the same variant is scheduled for compilation multiple times, the first scheduling
|
||||
* takes precedence; later scheduling are ignored.
|
||||
*</p>
|
||||
*<p>
|
||||
* caveat: A consequence is that if a variant is scheduled on the low priority queue and later
|
||||
* scheduled again on the high priority queue, the later scheduling is ignored.
|
||||
* Therefore, the second callback could be called before the variant is compiled.
|
||||
* However, the first callback, if specified, will trigger as expected.
|
||||
*</p>
|
||||
*<p>
|
||||
* The callback is guaranteed to be called. If the engine is destroyed while some material
|
||||
* variants are still compiling or in the queue, these will be discarded and the corresponding
|
||||
* callback will be called. In that case however the Material pointer passed to the callback
|
||||
* is guaranteed to be invalid (either because it's been destroyed by the user already, or,
|
||||
* because it's been cleaned-up by the Engine).
|
||||
*</p>
|
||||
*<p>
|
||||
* {@link UserVariantFilterBit#ALL} should be used with caution. Only variants that an application
|
||||
* needs should be included in the variants argument. For example, the STE variant is only used
|
||||
* for stereoscopic rendering. If an application is not planning to render in stereo, this bit
|
||||
* should be turned off to avoid unnecessary material compilations.
|
||||
*</p>
|
||||
* @param priority Which priority queue to use, LOW or HIGH.
|
||||
* @param variants Variants to include to the compile command.
|
||||
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
|
||||
* @param callback callback called on the main thread when the compilation is done on
|
||||
* by backend.
|
||||
*/
|
||||
public void compile(@NonNull CompilerPriorityQueue priority,
|
||||
int variants,
|
||||
@Nullable Object handler,
|
||||
@Nullable Runnable callback) {
|
||||
nCompile(getNativeObject(), priority.ordinal(), variants, handler, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of this material. Material instances should be freed using
|
||||
* {@link Engine#destroyMaterialInstance(MaterialInstance)}.
|
||||
@@ -437,6 +527,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.
|
||||
*
|
||||
@@ -916,6 +1028,7 @@ public class Material {
|
||||
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
|
||||
private static native long nGetDefaultInstance(long nativeMaterial);
|
||||
|
||||
private static native void nCompile(long nativeMaterial, int priority, int variants, Object handler, Runnable runnable);
|
||||
private static native String nGetName(long nativeMaterial);
|
||||
private static native int nGetShading(long nativeMaterial);
|
||||
private static native int nGetInterpolation(long nativeMaterial);
|
||||
@@ -932,6 +1045,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);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.google.android.filament;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
@@ -146,18 +147,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());
|
||||
@@ -179,6 +191,52 @@ public class Scene {
|
||||
return mNativeObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all entities in the Scene. If outArray is provided and large enough,
|
||||
* it is used to store the list and returned, otherwise a new array is allocated and returned.
|
||||
* @param outArray an array to store the list of entities in the scene.
|
||||
* @return outArray if it was used or a newly allocated array.
|
||||
* @see #getEntityCount
|
||||
*/
|
||||
public int[] getEntities(@Nullable int[] outArray) {
|
||||
int c = getEntityCount();
|
||||
if (outArray == null || outArray.length < c) {
|
||||
outArray = new int[c];
|
||||
}
|
||||
boolean success = nGetEntities(getNativeObject(), outArray, outArray.length);
|
||||
if (!success) {
|
||||
throw new IllegalStateException("Error retriving Scene's entities");
|
||||
}
|
||||
return outArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all entities in the Scene in a newly allocated array.
|
||||
* @return an array containing the list of all entities in the scene.
|
||||
* @see #getEntityCount
|
||||
*/
|
||||
public int[] getEntities() {
|
||||
return getEntities(null);
|
||||
}
|
||||
|
||||
public interface EntityProcessor {
|
||||
void process(@Entity int entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes user functor on each entity in the scene.
|
||||
*
|
||||
* It is not allowed to add or remove an entity from the scene within the functor.
|
||||
*
|
||||
* @param entityProcessor User provided functor called for each entity in the scene
|
||||
*/
|
||||
public void forEach(@NonNull EntityProcessor entityProcessor) {
|
||||
int[] entities = getEntities(null);
|
||||
for (int entity : entities) {
|
||||
entityProcessor.process(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void clearNativeObject() {
|
||||
mNativeObject = 0;
|
||||
}
|
||||
@@ -189,7 +247,9 @@ 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);
|
||||
private static native boolean nGetEntities(long nativeScene, int[] outArray, int length);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -849,6 +849,10 @@ public class Texture {
|
||||
public static final int SAMPLEABLE = 0x10;
|
||||
/** Texture can be used as a subpass input */
|
||||
public static final int SUBPASS_INPUT = 0x20;
|
||||
/** Texture can be used the source of a blit() */
|
||||
public static final int BLIT_SRC = 0x40;
|
||||
/** Texture can be used the destination of a blit() */
|
||||
public static final int BLIT_DST = 0x80;
|
||||
/** by default textures are <code>UPLOADABLE</code> and <code>SAMPLEABLE</code>*/
|
||||
public static final int DEFAULT = UPLOADABLE | SAMPLEABLE;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ public class View {
|
||||
private AmbientOcclusionOptions mAmbientOcclusionOptions;
|
||||
private BloomOptions mBloomOptions;
|
||||
private FogOptions mFogOptions;
|
||||
private StereoscopicOptions mStereoscopicOptions;
|
||||
private RenderTarget mRenderTarget;
|
||||
private BlendMode mBlendMode;
|
||||
private DepthOfFieldOptions mDepthOfFieldOptions;
|
||||
@@ -904,7 +905,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 +1033,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>
|
||||
@@ -1054,6 +1056,51 @@ public class View {
|
||||
return nIsStencilBufferEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stereoscopic rendering options for this view.
|
||||
*
|
||||
* <p>
|
||||
* Currently, only one type of stereoscopic rendering is supported: side-by-side.
|
||||
* Side-by-side stereo rendering splits the viewport into two halves: a left and right half.
|
||||
* Eye 0 will render to the left half, while Eye 1 will render into the right half.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Currently, the following features are not supported with stereoscopic rendering:
|
||||
* - post-processing
|
||||
* - shadowing
|
||||
* - punctual lights
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Stereo rendering depends on device and platform support. To check if stereo rendering is
|
||||
* supported, use {@link Engine#isStereoSupported()}. If stereo rendering is not supported, then
|
||||
* the stereoscopic options have no effect.
|
||||
* </p>
|
||||
*
|
||||
* @param options The stereoscopic options to use on this view
|
||||
* @see #getStereoscopicOptions
|
||||
*/
|
||||
public void setStereoscopicOptions(@NonNull StereoscopicOptions options) {
|
||||
mStereoscopicOptions = options;
|
||||
nSetStereoscopicOptions(getNativeObject(), options.enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stereoscopic options.
|
||||
*
|
||||
* @return options Stereoscopic options currently set.
|
||||
* @see #setStereoscopicOptions
|
||||
*/
|
||||
@NonNull
|
||||
public StereoscopicOptions getStereoscopicOptions() {
|
||||
if (mStereoscopicOptions == null) {
|
||||
mStereoscopicOptions = new StereoscopicOptions();
|
||||
}
|
||||
return mStereoscopicOptions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A class containing the result of a picking query
|
||||
*/
|
||||
@@ -1216,9 +1263,10 @@ 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 nSetStereoscopicOptions(long nativeView, boolean enabled);
|
||||
private static native void nSetBlendMode(long nativeView, int blendMode);
|
||||
private static native void nSetDepthOfFieldOptions(long nativeView, float cocScale, float maxApertureDiameter, boolean enabled, int filter,
|
||||
boolean nativeResolution, int foregroundRingCount, int backgroundRingCount, int fastGatherRingCount, int maxForegroundCOC, int maxBackgroundCOC);
|
||||
@@ -1353,8 +1401,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 +1439,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)
|
||||
*/
|
||||
@@ -1595,6 +1637,10 @@ public class View {
|
||||
* circle of confusion scale factor (amount of blur)
|
||||
*/
|
||||
public float cocScale = 1.0f;
|
||||
/**
|
||||
* width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
|
||||
*/
|
||||
public float cocAspectRatio = 1.0f;
|
||||
/**
|
||||
* maximum aperture diameter in meters (zero to disable rotation)
|
||||
*/
|
||||
@@ -1810,7 +1856,7 @@ public class View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for Temporal Multi-Sample Anti-aliasing (MSAA)
|
||||
* Options for Multi-Sample Anti-aliasing (MSAA)
|
||||
* @see setMultiSampleAntiAliasingOptions()
|
||||
*/
|
||||
public static class MultiSampleAntiAliasingOptions {
|
||||
@@ -1834,21 +1880,106 @@ public class View {
|
||||
|
||||
/**
|
||||
* Options for Temporal Anti-aliasing (TAA)
|
||||
* Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process
|
||||
* shaders to be recompiled. These options should be changed or set during initialization.
|
||||
* `filterWidth`, `feedback` and `jitterPattern`, however, could be changed at any time.
|
||||
*
|
||||
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
||||
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.
|
||||
*
|
||||
* @see setTemporalAntiAliasingOptions()
|
||||
*/
|
||||
public static class TemporalAntiAliasingOptions {
|
||||
public enum BoxType {
|
||||
/**
|
||||
* use an AABB neighborhood
|
||||
*/
|
||||
AABB,
|
||||
/**
|
||||
* use the variance of the neighborhood (not recommended)
|
||||
*/
|
||||
VARIANCE,
|
||||
/**
|
||||
* use both AABB and variance
|
||||
*/
|
||||
AABB_VARIANCE,
|
||||
}
|
||||
|
||||
public enum BoxClipping {
|
||||
/**
|
||||
* Accurate box clipping
|
||||
*/
|
||||
ACCURATE,
|
||||
/**
|
||||
* clamping
|
||||
*/
|
||||
CLAMP,
|
||||
/**
|
||||
* no rejections (use for debugging)
|
||||
*/
|
||||
NONE,
|
||||
}
|
||||
|
||||
public enum JitterPattern {
|
||||
RGSS_X4,
|
||||
UNIFORM_HELIX_X4,
|
||||
HALTON_23_X8,
|
||||
HALTON_23_X16,
|
||||
}
|
||||
|
||||
/**
|
||||
* reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother)
|
||||
* reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother)
|
||||
*/
|
||||
public float filterWidth = 1.0f;
|
||||
/**
|
||||
* history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
|
||||
*/
|
||||
public float feedback = 0.04f;
|
||||
public float feedback = 0.12f;
|
||||
/**
|
||||
* texturing lod bias (typically -1 or -2)
|
||||
*/
|
||||
public float lodBias = -1.0f;
|
||||
/**
|
||||
* enables or disables temporal anti-aliasing
|
||||
*/
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* 4x TAA upscaling. Disables Dynamic Resolution. [BETA]
|
||||
*/
|
||||
public boolean upscaling = false;
|
||||
/**
|
||||
* whether to filter the history buffer
|
||||
*/
|
||||
public boolean filterHistory = true;
|
||||
/**
|
||||
* whether to apply the reconstruction filter to the input
|
||||
*/
|
||||
public boolean filterInput = true;
|
||||
/**
|
||||
* whether to use the YcoCg color-space for history rejection
|
||||
*/
|
||||
public boolean useYCoCg = false;
|
||||
/**
|
||||
* type of color gamut box
|
||||
*/
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB;
|
||||
/**
|
||||
* clipping algorithm
|
||||
*/
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE;
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16;
|
||||
public float varianceGamma = 1.0f;
|
||||
/**
|
||||
* adjust the feedback dynamically to reduce flickering
|
||||
*/
|
||||
public boolean preventFlickering = false;
|
||||
/**
|
||||
* whether to apply history reprojection (debug option)
|
||||
*/
|
||||
public boolean historyReprojection = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1935,6 +2066,7 @@ public class View {
|
||||
* PCF with soft shadows and contact hardening
|
||||
*/
|
||||
PCSS,
|
||||
PCFd,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+184
-166
@@ -182,28 +182,85 @@ public class UiHelper {
|
||||
void detach();
|
||||
}
|
||||
|
||||
private static class SurfaceViewHandler implements RenderSurface {
|
||||
private final SurfaceView mSurfaceView;
|
||||
private class SurfaceViewHandler implements RenderSurface, SurfaceHolder.Callback {
|
||||
@NonNull private final SurfaceView mSurfaceView;
|
||||
|
||||
SurfaceViewHandler(SurfaceView surface) {
|
||||
mSurfaceView = surface;
|
||||
SurfaceViewHandler(@NonNull SurfaceView surfaceView) {
|
||||
mSurfaceView = surfaceView;
|
||||
|
||||
@NonNull SurfaceHolder holder = surfaceView.getHolder();
|
||||
holder.addCallback(this);
|
||||
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceView's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
surfaceCreated(holder);
|
||||
// there is no way to retrieve the actual PixelFormat, since it is not used
|
||||
// in the callback, we can use whatever we want.
|
||||
surfaceChanged(holder, PixelFormat.RGBA_8888,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
mSurfaceView.getHolder().setFixedSize(width, height);
|
||||
@NonNull SurfaceHolder holder = mSurfaceView.getHolder();
|
||||
holder.setFixedSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
@NonNull SurfaceHolder holder = mSurfaceView.getHolder();
|
||||
holder.removeCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SurfaceHolderHandler implements RenderSurface {
|
||||
private class SurfaceHolderHandler implements RenderSurface, SurfaceHolder.Callback {
|
||||
private final SurfaceHolder mSurfaceHolder;
|
||||
|
||||
SurfaceHolderHandler(SurfaceHolder surface) {
|
||||
mSurfaceHolder = surface;
|
||||
SurfaceHolderHandler(@NonNull SurfaceHolder holder) {
|
||||
mSurfaceHolder = holder;
|
||||
holder.addCallback(this);
|
||||
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceHolder's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
surfaceCreated(holder);
|
||||
// there is no way to retrieve the actual PixelFormat, since it is not used
|
||||
// in the callback, we can use whatever we want.
|
||||
surfaceChanged(holder, PixelFormat.RGBA_8888,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,30 +270,127 @@ public class UiHelper {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
mSurfaceHolder.removeCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
private class TextureViewHandler implements RenderSurface {
|
||||
private class TextureViewHandler implements RenderSurface, TextureView.SurfaceTextureListener {
|
||||
private final TextureView mTextureView;
|
||||
private Surface mSurface;
|
||||
|
||||
TextureViewHandler(TextureView surface) { mTextureView = surface; }
|
||||
TextureViewHandler(@NonNull TextureView view) {
|
||||
mTextureView = view;
|
||||
mTextureView.setSurfaceTextureListener(this);
|
||||
// in case the View's SurfaceTexture already existed
|
||||
if (view.isAvailable()) {
|
||||
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
this.onSurfaceTextureAvailable(surfaceTexture,
|
||||
mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
surfaceTexture.setDefaultBufferSize(width, height);
|
||||
}
|
||||
}
|
||||
if (mRenderCallback != null) {
|
||||
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
setSurface(null);
|
||||
mTextureView.setSurfaceTextureListener(null);
|
||||
}
|
||||
|
||||
void setSurface(Surface surface) {
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
final Surface surface = new Surface(surfaceTexture);
|
||||
setSurface(surface);
|
||||
createSwapChain(surface);
|
||||
|
||||
if (mRenderCallback != null) {
|
||||
// Call this the first time because onSurfaceTextureSizeChanged()
|
||||
// isn't called at initialization time
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
|
||||
if (mRenderCallback != null) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
|
||||
} else {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
// We must recreate the SwapChain to guarantee that it sees the new size.
|
||||
// More precisely, for an EGL client, the EGLSurface must be recreated. For
|
||||
// a Vulkan client, the SwapChain must be recreated. Calling
|
||||
// onNativeWindowChanged() will accomplish that.
|
||||
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
|
||||
// documentation.
|
||||
final Surface surface = getSurface();
|
||||
if (surface != null) {
|
||||
mRenderCallback.onNativeWindowChanged(surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
setSurface(null);
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { }
|
||||
|
||||
|
||||
private void setSurface(@Nullable Surface surface) {
|
||||
if (surface == null) {
|
||||
if (mSurface != null) {
|
||||
mSurface.release();
|
||||
@@ -245,7 +399,7 @@ public class UiHelper {
|
||||
mSurface = surface;
|
||||
}
|
||||
|
||||
public Surface getSurface() {
|
||||
private Surface getSurface() {
|
||||
return mSurface;
|
||||
}
|
||||
}
|
||||
@@ -291,6 +445,9 @@ public class UiHelper {
|
||||
* {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}.
|
||||
*/
|
||||
public void detach() {
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
}
|
||||
destroySwapChain();
|
||||
mNativeWindow = null;
|
||||
mRenderSurface = null;
|
||||
@@ -298,7 +455,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 +499,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 +521,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 +543,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.
|
||||
*/
|
||||
@@ -405,171 +557,32 @@ public class UiHelper {
|
||||
view.setZOrderOnTop(translucent);
|
||||
}
|
||||
|
||||
int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
|
||||
view.getHolder().setFormat(format);
|
||||
|
||||
view.getHolder().setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT);
|
||||
mRenderSurface = new SurfaceViewHandler(view);
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
};
|
||||
|
||||
SurfaceHolder holder = view.getHolder();
|
||||
holder.addCallback(callback);
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceView's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
callback.surfaceCreated(holder);
|
||||
callback.surfaceChanged(holder, format,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void attachTo(@NonNull TextureView view) {
|
||||
if (attach(view)) {
|
||||
view.setOpaque(isOpaque());
|
||||
|
||||
mRenderSurface = new TextureViewHandler(view);
|
||||
|
||||
TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
Surface surface = new Surface(surfaceTexture);
|
||||
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
|
||||
textureViewHandler.setSurface(surface);
|
||||
|
||||
createSwapChain(surface);
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
// 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) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
|
||||
};
|
||||
|
||||
view.setSurfaceTextureListener(listener);
|
||||
|
||||
// in case the View's SurfaceTexture already existed
|
||||
if (view.isAvailable()) {
|
||||
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
|
||||
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.
|
||||
*/
|
||||
public void attachTo(@NonNull SurfaceHolder holder) {
|
||||
if (attach(holder)) {
|
||||
int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
|
||||
holder.setFormat(format);
|
||||
|
||||
holder.setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT);
|
||||
mRenderSurface = new SurfaceHolderHandler(holder);
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
};
|
||||
|
||||
holder.addCallback(callback);
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceHolder's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
callback.surfaceCreated(holder);
|
||||
callback.surfaceChanged(holder, format,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,6 +593,10 @@ public class UiHelper {
|
||||
// nothing to do
|
||||
return false;
|
||||
}
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
mRenderSurface = null;
|
||||
}
|
||||
destroySwapChain();
|
||||
}
|
||||
mNativeWindow = nativeWindow;
|
||||
@@ -587,15 +604,16 @@ public class UiHelper {
|
||||
}
|
||||
|
||||
private void createSwapChain(@NonNull Surface surface) {
|
||||
mRenderCallback.onNativeWindowChanged(surface);
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onNativeWindowChanged(surface);
|
||||
}
|
||||
mHasSwapChain = true;
|
||||
}
|
||||
|
||||
private void destroySwapChain() {
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onDetachedFromSurface();
|
||||
}
|
||||
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
-1
@@ -144,7 +144,7 @@ public class FilamentInstance {
|
||||
*
|
||||
* Ignored if variantIndex is out of bounds.
|
||||
*/
|
||||
void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
|
||||
public void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
|
||||
nApplyMaterialVariant(mNativeObject, variantIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.43.1
|
||||
VERSION_NAME=1.49.3
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Wed Nov 17 10:40:18 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -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
|
||||
|
||||
+31
@@ -26,6 +26,7 @@ import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.google.android.filament.Fence
|
||||
import com.google.android.filament.IndirectLight
|
||||
import com.google.android.filament.Material
|
||||
import com.google.android.filament.Skybox
|
||||
import com.google.android.filament.View
|
||||
import com.google.android.filament.View.OnPickCallback
|
||||
@@ -392,6 +393,36 @@ class MainActivity : Activity() {
|
||||
Log.i(TAG, "The Filament backend took $total ms to load the model geometry.")
|
||||
modelViewer.engine.destroyFence(it)
|
||||
loadStartFence = null
|
||||
|
||||
val materials = mutableSetOf<Material>()
|
||||
val rcm = modelViewer.engine.renderableManager
|
||||
modelViewer.scene.forEach {
|
||||
val entity = it
|
||||
if (rcm.hasComponent(entity)) {
|
||||
val ri = rcm.getInstance(entity)
|
||||
val c = rcm.getPrimitiveCount(ri)
|
||||
for (i in 0 until c) {
|
||||
val mi = rcm.getMaterialInstanceAt(ri, i)
|
||||
val ma = mi.material
|
||||
materials.add(ma)
|
||||
}
|
||||
}
|
||||
}
|
||||
materials.forEach {
|
||||
it.compile(
|
||||
Material.CompilerPriorityQueue.HIGH,
|
||||
Material.UserVariantFilterBit.DIRECTIONAL_LIGHTING or
|
||||
Material.UserVariantFilterBit.DYNAMIC_LIGHTING or
|
||||
Material.UserVariantFilterBit.SHADOW_RECEIVER,
|
||||
null, null)
|
||||
it.compile(
|
||||
Material.CompilerPriorityQueue.LOW,
|
||||
Material.UserVariantFilterBit.FOG or
|
||||
Material.UserVariantFilterBit.SKINNING or
|
||||
Material.UserVariantFilterBit.SSR or
|
||||
Material.UserVariantFilterBit.VSM,
|
||||
null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -20,6 +20,8 @@ import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.opengl.Matrix
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.Choreographer
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceView
|
||||
@@ -110,7 +112,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 +122,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,12 +157,16 @@ 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)
|
||||
material.compile(
|
||||
Material.CompilerPriorityQueue.HIGH,
|
||||
Material.UserVariantFilterBit.ALL,
|
||||
Handler(Looper.getMainLooper())) {
|
||||
android.util.Log.i("hellotriangle",
|
||||
"Material " + material.name + " compiled.")
|
||||
}
|
||||
engine.flush()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -50,7 +50,7 @@ function replace {
|
||||
FIND_STR="${1//\{\{VERSION\}\}/${VERSION_REGEX}}"
|
||||
REPLACE_STR="${1//\{\{VERSION\}\}/${NEW_VERSION}}"
|
||||
local FILE_NAME="$2"
|
||||
if [ IS_DARWIN ]; then
|
||||
if [ $IS_DARWIN == 1 ]; then
|
||||
sed -i '' -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
else
|
||||
sed -i -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
|
||||
@@ -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
|
||||
|
||||
+19
-8
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>
|
||||
|
||||
+19
-8
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -587,6 +587,7 @@
|
||||
<li><em>overrides</em> Dictionary with one or more of the following properties: mapSize, shadowCascades, constantBias, normalBias, shadowFar, shadowNearHint, shadowFarHint, stable, polygonOffsetConstant, polygonOffsetSlope, \</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>engine.setStereoscopicOptions()</strong></li>
|
||||
<li><strong>engine.setTemporalAntiAliasingOptions()</strong></li>
|
||||
<li><strong>engine.setVignetteOptions()</strong></li>
|
||||
</ul>
|
||||
@@ -1462,6 +1463,8 @@ This defines the following functions:</p>
|
||||
<li>STENCIL_ATTACHMENT</li>
|
||||
<li>UPLOADABLE</li>
|
||||
<li>SAMPLEABLE</li>
|
||||
<li>BLIT_SRC</li>
|
||||
<li>BLIT_DST</li>
|
||||
<li>SUBPASS_INPUT</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
|
||||
+28
-22
@@ -209,10 +209,13 @@ set(PRIVATE_HDRS
|
||||
)
|
||||
|
||||
set(MATERIAL_SRCS
|
||||
src/materials/fsr/fsr_easu.mat
|
||||
src/materials/fsr/fsr_easu_mobile.mat
|
||||
src/materials/fsr/fsr_easu_mobileF.mat
|
||||
src/materials/fsr/fsr_rcas.mat
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
src/materials/bloom/bloomUpsample.mat
|
||||
src/materials/colorGrading/colorGrading.mat
|
||||
src/materials/colorGrading/colorGradingAsSubpass.mat
|
||||
src/materials/colorGrading/customResolveAsSubpass.mat
|
||||
@@ -220,32 +223,30 @@ set(MATERIAL_SRCS
|
||||
src/materials/defaultMaterial.mat
|
||||
src/materials/dof/dof.mat
|
||||
src/materials/dof/dofCoc.mat
|
||||
src/materials/dof/dofDownsample.mat
|
||||
src/materials/dof/dofCombine.mat
|
||||
src/materials/dof/dofDilate.mat
|
||||
src/materials/dof/dofDownsample.mat
|
||||
src/materials/dof/dofMedian.mat
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofTiles.mat
|
||||
src/materials/dof/dofTilesSwizzle.mat
|
||||
src/materials/dof/dofDilate.mat
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofMedian.mat
|
||||
src/materials/flare/flare.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
src/materials/bloom/bloomUpsample.mat
|
||||
src/materials/fsr/fsr_easu.mat
|
||||
src/materials/fsr/fsr_easu_mobile.mat
|
||||
src/materials/fsr/fsr_easu_mobileF.mat
|
||||
src/materials/fsr/fsr_rcas.mat
|
||||
src/materials/resolveDepth.mat
|
||||
src/materials/separableGaussianBlur.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/ssao/bilateralBlur.mat
|
||||
src/materials/ssao/bilateralBlurBentNormals.mat
|
||||
src/materials/ssao/mipmapDepth.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/ssao/sao.mat
|
||||
src/materials/ssao/saoBentNormals.mat
|
||||
src/materials/separableGaussianBlur.mat
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/vsmMipmap.mat
|
||||
)
|
||||
|
||||
set(MATERIAL_ES2_SRCS
|
||||
set(MATERIAL_FL0_SRCS
|
||||
src/materials/defaultMaterial0.mat
|
||||
src/materials/skybox0.mat
|
||||
)
|
||||
@@ -274,6 +275,11 @@ if (NOT DFG_LUT_SIZE)
|
||||
endif()
|
||||
message(STATUS "DFG LUT size set to ${DFG_LUT_SIZE}x${DFG_LUT_SIZE}")
|
||||
|
||||
# Whether to include FL0 materials.
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
add_definitions(-DFILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Definitions
|
||||
# ==================================================================================================
|
||||
@@ -315,15 +321,15 @@ 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})
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
foreach (mat_src ${MATERIAL_FL0_SRCS})
|
||||
get_filename_component(localname "${mat_src}" NAME_WE)
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
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}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
@@ -557,7 +563,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
|
||||
@@ -168,6 +169,8 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
src/vulkan/spirv/VulkanSpirvUtils.cpp
|
||||
src/vulkan/spirv/VulkanSpirvUtils.h
|
||||
src/vulkan/VulkanBlitter.cpp
|
||||
src/vulkan/VulkanBlitter.h
|
||||
src/vulkan/VulkanBuffer.cpp
|
||||
@@ -312,6 +315,7 @@ endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc vkshaders smol-v)
|
||||
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_METAL)
|
||||
@@ -355,7 +359,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
|
||||
@@ -396,8 +400,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include)
|
||||
# ==================================================================================================
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
|
||||
if (APPLE)
|
||||
add_library(backend_test STATIC
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
@@ -409,19 +413,26 @@ if (APPLE)
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_RenderExternalImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(backend_test PRIVATE
|
||||
)
|
||||
set(BACKEND_TEST_LIBS
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_RenderExternalImage.cpp)
|
||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test PRIVATE ${BACKEND_TEST_LIBS})
|
||||
|
||||
set(BACKEND_TEST_DEPS
|
||||
OGLCompiler
|
||||
@@ -435,12 +446,11 @@ if (APPLE)
|
||||
glslang
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-msl
|
||||
)
|
||||
spirv-cross-msl)
|
||||
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
|
||||
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
|
||||
@@ -454,15 +464,21 @@ if (APPLE)
|
||||
endif()
|
||||
|
||||
set_target_properties(backend_test PROPERTIES FOLDER Tests)
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
if (LINUX)
|
||||
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
|
||||
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
/**
|
||||
* Helper to create a BufferDescriptor that uses a KNOWN method pointer w/ object passed
|
||||
* by pointer as the callback. e.g.:
|
||||
* auto bd = BufferDescriptor::make(buffer, size, &Foo::method, foo);
|
||||
* auto bd = BufferDescriptor::make<Foo, &Foo::method>(buffer, size, foo);
|
||||
*
|
||||
* @param buffer Memory address of the CPU buffer to reference
|
||||
* @param size Size of the CPU buffer in bytes
|
||||
@@ -121,12 +121,12 @@ public:
|
||||
* @return a new BufferDescriptor
|
||||
*/
|
||||
template<typename T, void(T::*method)(void const*, size_t)>
|
||||
static BufferDescriptor make(
|
||||
void const* buffer, size_t size, T* data, CallbackHandler* handler = nullptr) noexcept {
|
||||
static BufferDescriptor make(void const* buffer, size_t size, T* data,
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return {
|
||||
buffer, size,
|
||||
handler, [](void* b, size_t s, void* u) {
|
||||
(*static_cast<T**>(u)->*method)(b, s);
|
||||
(static_cast<T*>(u)->*method)(b, s);
|
||||
}, data
|
||||
};
|
||||
}
|
||||
@@ -145,14 +145,14 @@ public:
|
||||
* @return a new BufferDescriptor
|
||||
*/
|
||||
template<typename T>
|
||||
static BufferDescriptor make(
|
||||
void const* buffer, size_t size, T&& functor, CallbackHandler* handler = nullptr) noexcept {
|
||||
static BufferDescriptor make(void const* buffer, size_t size, T&& functor,
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return {
|
||||
buffer, size,
|
||||
handler, [](void* b, size_t s, void* u) {
|
||||
T& that = *static_cast<T*>(u);
|
||||
that(b, s);
|
||||
delete &that;
|
||||
T* const that = static_cast<T*>(u);
|
||||
that->operator()(b, s);
|
||||
delete that;
|
||||
},
|
||||
new T(std::forward<T>(functor))
|
||||
};
|
||||
@@ -201,7 +201,7 @@ public:
|
||||
return mUser;
|
||||
}
|
||||
|
||||
//! CPU mempry-buffer virtual address
|
||||
//! CPU memory-buffer virtual address
|
||||
void* buffer = nullptr;
|
||||
|
||||
//! CPU memory-buffer size in bytes
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -198,6 +227,7 @@ struct Viewport {
|
||||
int32_t top() const noexcept { return bottom + int32_t(height); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the mapping of the near and far clipping plane to window coordinates.
|
||||
*/
|
||||
@@ -629,13 +659,15 @@ enum class TextureFormat : uint16_t {
|
||||
|
||||
//! Bitmask describing the intended Texture Usage
|
||||
enum class TextureUsage : uint8_t {
|
||||
NONE = 0x0,
|
||||
COLOR_ATTACHMENT = 0x1, //!< Texture can be used as a color attachment
|
||||
DEPTH_ATTACHMENT = 0x2, //!< Texture can be used as a depth attachment
|
||||
STENCIL_ATTACHMENT = 0x4, //!< Texture can be used as a stencil attachment
|
||||
UPLOADABLE = 0x8, //!< Data can be uploaded into this texture (default)
|
||||
NONE = 0x00,
|
||||
COLOR_ATTACHMENT = 0x01, //!< Texture can be used as a color attachment
|
||||
DEPTH_ATTACHMENT = 0x02, //!< Texture can be used as a depth attachment
|
||||
STENCIL_ATTACHMENT = 0x04, //!< Texture can be used as a stencil attachment
|
||||
UPLOADABLE = 0x08, //!< Data can be uploaded into this texture (default)
|
||||
SAMPLEABLE = 0x10, //!< Texture can be sampled (default)
|
||||
SUBPASS_INPUT = 0x20, //!< Texture can be used as a subpass input
|
||||
BLIT_SRC = 0x40, //!< Texture can be used the source of a blit()
|
||||
BLIT_DST = 0x80, //!< Texture can be used the destination of a blit()
|
||||
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
|
||||
};
|
||||
|
||||
@@ -663,6 +695,17 @@ static constexpr bool isDepthFormat(TextureFormat format) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr bool isStencilFormat(TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::STENCIL8:
|
||||
case TextureFormat::DEPTH24_STENCIL8:
|
||||
case TextureFormat::DEPTH32F_STENCIL8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr bool isUnsignedIntFormat(TextureFormat format) {
|
||||
switch (format) {
|
||||
case TextureFormat::R8UI:
|
||||
@@ -1151,8 +1194,8 @@ using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
||||
enum class Workaround : uint16_t {
|
||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||
SPLIT_EASU,
|
||||
// Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're read-only for
|
||||
// the whole render pass.
|
||||
// Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're
|
||||
// read-only for the whole render pass.
|
||||
ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP,
|
||||
// for some uniform arrays, it's needed to do an initialization to avoid crash on adreno gpu
|
||||
ADRENO_UNIFORM_ARRAY_CRASH,
|
||||
|
||||
@@ -141,7 +141,7 @@ public:
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return { buffer, size, format, type, alignment, left, top, stride,
|
||||
handler, [](void* b, size_t s, void* u) {
|
||||
(*static_cast<T**>(u)->*method)(b, s); }, data };
|
||||
(static_cast<T*>(u)->*method)(b, s); }, data };
|
||||
}
|
||||
|
||||
template<typename T, void(T::*method)(void const*, size_t)>
|
||||
@@ -149,7 +149,7 @@ public:
|
||||
PixelDataFormat format, PixelDataType type, T* data,
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return { buffer, size, format, type, handler, [](void* b, size_t s, void* u) {
|
||||
(*static_cast<T**>(u)->*method)(b, s); }, data };
|
||||
(static_cast<T*>(u)->*method)(b, s); }, data };
|
||||
}
|
||||
|
||||
template<typename T, void(T::*method)(void const*, size_t)>
|
||||
@@ -157,7 +157,7 @@ public:
|
||||
backend::CompressedPixelDataType format, uint32_t imageSize, T* data,
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return { buffer, size, format, imageSize, handler, [](void* b, size_t s, void* u) {
|
||||
(*static_cast<T**>(u)->*method)(b, s); }, data
|
||||
(static_cast<T*>(u)->*method)(b, s); }, data
|
||||
};
|
||||
}
|
||||
|
||||
@@ -168,9 +168,9 @@ public:
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return { buffer, size, format, type, alignment, left, top, stride,
|
||||
handler, [](void* b, size_t s, void* u) {
|
||||
T& that = *static_cast<T*>(u);
|
||||
that(b, s);
|
||||
delete &that;
|
||||
T* const that = static_cast<T*>(u);
|
||||
that->operator()(b, s);
|
||||
delete that;
|
||||
}, new T(std::forward<T>(functor))
|
||||
};
|
||||
}
|
||||
@@ -181,9 +181,9 @@ public:
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return { buffer, size, format, type,
|
||||
handler, [](void* b, size_t s, void* u) {
|
||||
T& that = *static_cast<T*>(u);
|
||||
that(b, s);
|
||||
delete &that;
|
||||
T* const that = static_cast<T*>(u);
|
||||
that->operator()(b, s);
|
||||
delete that;
|
||||
}, new T(std::forward<T>(functor))
|
||||
};
|
||||
}
|
||||
@@ -194,9 +194,9 @@ public:
|
||||
CallbackHandler* handler = nullptr) noexcept {
|
||||
return { buffer, size, format, imageSize,
|
||||
handler, [](void* b, size_t s, void* u) {
|
||||
T& that = *static_cast<T*>(u);
|
||||
that(b, s);
|
||||
delete &that;
|
||||
T* const that = static_cast<T*>(u);
|
||||
that->operator()(b, s);
|
||||
delete that;
|
||||
}, new T(std::forward<T>(functor))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -300,10 +300,10 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, canGenerateMipmaps)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
|
||||
@@ -468,7 +468,7 @@ DECL_DRIVER_API_N(readBufferSubData,
|
||||
* --------------------
|
||||
*/
|
||||
|
||||
DECL_DRIVER_API_N(blit,
|
||||
DECL_DRIVER_API_N(blitDEPRECATED,
|
||||
backend::TargetBufferFlags, buffers,
|
||||
backend::RenderTargetHandle, dst,
|
||||
backend::Viewport, dstRect,
|
||||
@@ -476,6 +476,21 @@ DECL_DRIVER_API_N(blit,
|
||||
backend::Viewport, srcRect,
|
||||
backend::SamplerMagFilter, filter)
|
||||
|
||||
DECL_DRIVER_API_N(resolve,
|
||||
backend::TextureHandle, dst,
|
||||
uint8_t, dstLevel, uint8_t, dstLayer,
|
||||
backend::TextureHandle, src,
|
||||
uint8_t, srcLevel, uint8_t, srcLayer)
|
||||
|
||||
DECL_DRIVER_API_N(blit,
|
||||
backend::TextureHandle, dst,
|
||||
uint8_t, dstLevel, uint8_t, dstLayer,
|
||||
math::uint2, dstOrigin,
|
||||
backend::TextureHandle, src,
|
||||
uint8_t, srcLevel, uint8_t, srcLayer,
|
||||
math::uint2, srcOrigin,
|
||||
math::uint2, size)
|
||||
|
||||
DECL_DRIVER_API_N(draw,
|
||||
backend::PipelineState, state,
|
||||
backend::RenderPrimitiveHandle, rph,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
#include <tsl/robin_map.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
namespace filament::backend {
|
||||
|
||||
struct MetalContext;
|
||||
|
||||
@@ -37,8 +36,7 @@ public:
|
||||
|
||||
struct BlitArgs {
|
||||
struct Attachment {
|
||||
id<MTLTexture> color = nil;
|
||||
id<MTLTexture> depth = nil;
|
||||
id<MTLTexture> texture = nil;
|
||||
MTLRegion region = {};
|
||||
uint8_t level = 0;
|
||||
uint32_t slice = 0; // must be 0 on source attachment
|
||||
@@ -46,25 +44,13 @@ public:
|
||||
|
||||
// Valid source formats: 2D, 2DArray, 2DMultisample, 3D
|
||||
// Valid destination formats: 2D, 2DArray, 3D, Cube
|
||||
Attachment source, destination;
|
||||
Attachment source;
|
||||
Attachment destination;
|
||||
SamplerMagFilter filter;
|
||||
|
||||
bool blitColor() const {
|
||||
return source.color != nil && destination.color != nil;
|
||||
}
|
||||
|
||||
bool blitDepth() const {
|
||||
return source.depth != nil && destination.depth != nil;
|
||||
}
|
||||
|
||||
bool colorDestinationIsFullAttachment() const {
|
||||
return destination.color.width == destination.region.size.width &&
|
||||
destination.color.height == destination.region.size.height;
|
||||
}
|
||||
|
||||
bool depthDestinationIsFullAttachment() const {
|
||||
return destination.depth.width == destination.region.size.width &&
|
||||
destination.depth.height == destination.region.size.height;
|
||||
bool destinationIsFullAttachment() const {
|
||||
return destination.texture.width == destination.region.size.width &&
|
||||
destination.texture.height == destination.region.size.height;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,48 +64,36 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
static void setupColorAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane);
|
||||
static void setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane);
|
||||
static void setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor,
|
||||
const BlitArgs& args, uint32_t depthPlane);
|
||||
|
||||
struct BlitFunctionKey {
|
||||
bool blitColor;
|
||||
bool blitDepth;
|
||||
bool msaaColorSource;
|
||||
bool msaaDepthSource;
|
||||
bool sources3D;
|
||||
|
||||
char padding[3];
|
||||
bool msaaColorSource{};
|
||||
bool sources3D{};
|
||||
char padding[2]{};
|
||||
|
||||
bool isValid() const noexcept {
|
||||
// MSAA 3D textures do not exist.
|
||||
bool hasMsaa = msaaColorSource || msaaDepthSource;
|
||||
bool const hasMsaa = msaaColorSource;
|
||||
return !(hasMsaa && sources3D);
|
||||
}
|
||||
|
||||
bool operator==(const BlitFunctionKey& rhs) const noexcept {
|
||||
return blitColor == rhs.blitColor &&
|
||||
blitDepth == rhs.blitDepth &&
|
||||
msaaColorSource == rhs.msaaColorSource &&
|
||||
msaaDepthSource == rhs.msaaDepthSource &&
|
||||
return msaaColorSource == rhs.msaaColorSource &&
|
||||
sources3D == rhs.sources3D;
|
||||
}
|
||||
|
||||
BlitFunctionKey() {
|
||||
std::memset(this, 0, sizeof(BlitFunctionKey));
|
||||
}
|
||||
};
|
||||
|
||||
void blitFastPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
static bool blitFastPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label);
|
||||
void blitSlowPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
|
||||
void blitSlowPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label);
|
||||
void blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor, bool blitDepth,
|
||||
const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest,
|
||||
const char* label);
|
||||
id<MTLTexture> createIntermediateTexture(id<MTLTexture> t, MTLSize size);
|
||||
id<MTLFunction> compileFragmentFunction(BlitFunctionKey key);
|
||||
|
||||
void blitDepthPlane(id <MTLCommandBuffer> cmdBuffer, const BlitArgs& args,
|
||||
uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label);
|
||||
|
||||
id<MTLFunction> compileFragmentFunction(BlitFunctionKey key) const;
|
||||
id<MTLFunction> getBlitVertexFunction();
|
||||
id<MTLFunction> getBlitFragmentFunction(BlitFunctionKey key);
|
||||
|
||||
@@ -130,10 +104,9 @@ private:
|
||||
tsl::robin_map<BlitFunctionKey, Function, HashFn> mBlitFunctions;
|
||||
|
||||
id<MTLFunction> mVertexFunction = nil;
|
||||
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
#endif //TNT_METALBLITTER_H
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
namespace filament::backend {
|
||||
|
||||
static const char* functionLibrary = R"(
|
||||
#include <metal_stdlib>
|
||||
@@ -37,13 +36,7 @@ struct VertexOut
|
||||
|
||||
struct FragmentOut
|
||||
{
|
||||
#ifdef BLIT_COLOR
|
||||
float4 color [[color(0)]];
|
||||
#endif
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
float depth [[depth(any)]];
|
||||
#endif
|
||||
};
|
||||
|
||||
vertex VertexOut
|
||||
@@ -67,7 +60,6 @@ fragment FragmentOut
|
||||
blitterFrag(VertexOut in [[stage_in]],
|
||||
sampler sourceSampler [[sampler(0)]],
|
||||
|
||||
#ifdef BLIT_COLOR
|
||||
#ifdef MSAA_COLOR_SOURCE
|
||||
texture2d_ms<float, access::read> sourceColor [[texture(0)]],
|
||||
#elif SOURCES_3D
|
||||
@@ -75,32 +67,18 @@ blitterFrag(VertexOut in [[stage_in]],
|
||||
#else
|
||||
texture2d<float, access::sample> sourceColor [[texture(0)]],
|
||||
#endif // MSAA_COLOR_SOURCE
|
||||
#endif // BLIT_COLOR
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
#ifdef MSAA_DEPTH_SOURCE
|
||||
texture2d_ms<float, access::read> sourceDepth [[texture(1)]],
|
||||
#elif SOURCES_3D
|
||||
texture3d<float, access::sample> sourceDepth [[texture(1)]],
|
||||
#else
|
||||
texture2d<float, access::sample> sourceDepth [[texture(1)]],
|
||||
#endif // MSAA_DEPTH_SOURCE
|
||||
#endif // BLIT_DEPTH
|
||||
|
||||
constant FragmentArgs* args [[buffer(0)]])
|
||||
{
|
||||
FragmentOut out = {};
|
||||
|
||||
#if defined(BLIT_COLOR) || defined(BLIT_DEPTH)
|
||||
// These coordinates match the Vulkan vkCmdBlitImage spec:
|
||||
// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdBlitImage.html
|
||||
float2 uvbase = in.position.xy; // unnormalized coordinates at center of texel: (1.5, 2.5, etc)
|
||||
float2 uvoffset = uvbase - args->dstOffset;
|
||||
float2 uvscaled = uvoffset * args->scale;
|
||||
float2 uv = uvscaled + args->srcOffset;
|
||||
#endif
|
||||
|
||||
#ifdef BLIT_COLOR
|
||||
#ifdef MSAA_COLOR_SOURCE
|
||||
out.color = float4(0.0);
|
||||
for (uint s = 0; s < sourceColor.get_num_samples(); s++) {
|
||||
@@ -116,201 +94,106 @@ blitterFrag(VertexOut in [[stage_in]],
|
||||
float2 uvnorm = uv / float2(sourceColor.get_width(args->lod), sourceColor.get_height(args->lod));
|
||||
out.color += sourceColor.sample(sourceSampler, uvnorm, level(args->lod));
|
||||
#endif // MSAA_COLOR_SOURCE
|
||||
#endif // BLIT_COLOR
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
#ifdef MSAA_DEPTH_SOURCE
|
||||
out.depth = 0.0;
|
||||
for (uint s = 0; s < sourceDepth.get_num_samples(); s++) {
|
||||
out.depth += sourceDepth.read(static_cast<uint2>(uv), s).r;
|
||||
}
|
||||
out.depth /= sourceDepth.get_num_samples();
|
||||
#elif SOURCES_3D
|
||||
float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod));
|
||||
float3 coords = float3(uvnormd, (static_cast<float>(args->depthPlane) + 0.5) /
|
||||
sourceDepth.get_depth(args->lod));
|
||||
out.depth = sourceDepth.sample(sourceSampler, coords, level(args->lod)).r;
|
||||
#else
|
||||
float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod));
|
||||
out.depth = sourceDepth.sample(sourceSampler, uvnormd, level(args->lod)).r;
|
||||
#endif // MSAA_DEPTH_SOURCE
|
||||
#endif // BLIT_DEPTH
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline bool MTLSizeEqual(T a, T b) noexcept {
|
||||
return (a.width == b.width && a.height == b.height && a.depth == b.depth);
|
||||
}
|
||||
|
||||
MetalBlitter::MetalBlitter(MetalContext& context) noexcept : mContext(context) { }
|
||||
|
||||
#define MTLSizeEqual(a, b) (a.width == b.width && a.height == b.height && a.depth == b.depth)
|
||||
|
||||
void MetalBlitter::blit(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args, const char* label) {
|
||||
bool blitColor = args.blitColor();
|
||||
bool blitDepth = args.blitDepth();
|
||||
|
||||
ASSERT_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth,
|
||||
"Blitting requires the source and destination regions to have the same depth.");
|
||||
|
||||
if (args.source.color && args.source.depth) {
|
||||
MTLTextureType colorType = args.source.color.textureType;
|
||||
MTLTextureType depthType = args.source.depth.textureType;
|
||||
|
||||
if (colorType == MTLTextureType2DMultisample) colorType = MTLTextureType2D;
|
||||
if (depthType == MTLTextureType2DMultisample) depthType = MTLTextureType2D;
|
||||
|
||||
ASSERT_PRECONDITION(colorType == depthType,
|
||||
"Blitting requires color and depth sources to be the same texture type.");
|
||||
}
|
||||
|
||||
// Determine if the blit for color or depth are eligible to use a MTLBlitCommandEncoder.
|
||||
// blitColor and / or blitDepth are set to false upon success, to indicate that no more work is
|
||||
// necessary for that attachment.
|
||||
blitFastPath(cmdBuffer, blitColor, blitDepth, args, label);
|
||||
|
||||
if (!blitColor && !blitDepth) {
|
||||
// blitFastPath returns true upon success.
|
||||
// blitFastPath fails if either the format or sampleCount don't match (i.e. resolve)
|
||||
if (blitFastPath(cmdBuffer, args, label)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the destination is MSAA and we weren't able to use the fast path, report an error, as
|
||||
// blitting to a MSAA texture isn't supported through the "slow path" yet.
|
||||
const bool colorDestinationIsMultisample =
|
||||
blitColor && args.destination.color.textureType == MTLTextureType2DMultisample;
|
||||
const bool depthDestinationIsMultisample =
|
||||
blitDepth && args.destination.depth.textureType == MTLTextureType2DMultisample;
|
||||
ASSERT_PRECONDITION(!colorDestinationIsMultisample && !depthDestinationIsMultisample,
|
||||
"Blitting between MSAA render targets with differing pixel formats and/or regions is not supported.");
|
||||
// If we end-up here, it means that either:
|
||||
// - we're resolving a color texture
|
||||
// - src/dest formats didn't match, or we're scaling -- this can only happen with the legacy
|
||||
// blit() path and implies that the format is not depth.
|
||||
// note: in the future we will support a fast-path resolve
|
||||
|
||||
// If the destination texture doesn't have the MTLTextureUsageRenderTarget flag, we have to blit
|
||||
// to an intermediate texture first to perform the format conversion. Then, we can perform a
|
||||
// "fast blit" to the final destination texture.
|
||||
UTILS_UNUSED_IN_RELEASE
|
||||
const bool destinationIsMultisample =
|
||||
args.destination.texture.textureType == MTLTextureType2DMultisample;
|
||||
|
||||
id<MTLTexture> intermediateColor = nil;
|
||||
id<MTLTexture> intermediateDepth = nil;
|
||||
BlitArgs slowBlit = args;
|
||||
BlitArgs finalBlit = args;
|
||||
|
||||
MTLRegion sourceRegionNoOffset = MTLRegionMake3D(0, 0, 0,
|
||||
args.source.region.size.width, args.source.region.size.height,
|
||||
args.source.region.size.depth);
|
||||
|
||||
if (blitColor && !(args.destination.color.usage & MTLTextureUsageRenderTarget)) {
|
||||
intermediateColor = createIntermediateTexture(args.destination.color, args.source.region.size);
|
||||
slowBlit.destination.color = finalBlit.source.color = intermediateColor;
|
||||
slowBlit.destination.level = finalBlit.source.level = 0;
|
||||
slowBlit.destination.slice = finalBlit.source.slice = 0;
|
||||
slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset;
|
||||
}
|
||||
if (blitDepth && !(args.destination.depth.usage & MTLTextureUsageRenderTarget)) {
|
||||
intermediateDepth = createIntermediateTexture(args.destination.depth, args.source.region.size);
|
||||
slowBlit.destination.depth = finalBlit.source.depth = intermediateDepth;
|
||||
slowBlit.destination.level = finalBlit.source.level = 0;
|
||||
slowBlit.destination.slice = finalBlit.source.slice = 0;
|
||||
slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset;
|
||||
}
|
||||
|
||||
blitSlowPath(cmdBuffer, blitColor, blitDepth, slowBlit, label);
|
||||
|
||||
bool finalBlitColor = intermediateColor != nil;
|
||||
bool finalBlitDepth = intermediateDepth != nil;
|
||||
blitFastPath(cmdBuffer, finalBlitColor, finalBlitDepth, finalBlit, label);
|
||||
assert_invariant(!destinationIsMultisample);
|
||||
assert_invariant((args.destination.texture.usage & MTLTextureUsageRenderTarget));
|
||||
blitSlowPath(cmdBuffer, args, label);
|
||||
}
|
||||
|
||||
void MetalBlitter::blitFastPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
bool MetalBlitter::blitFastPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label) {
|
||||
if (blitColor) {
|
||||
if (args.source.color.sampleCount == args.destination.color.sampleCount &&
|
||||
args.source.color.pixelFormat == args.destination.color.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.color
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.color
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
if (args.source.texture.sampleCount == args.destination.texture.sampleCount &&
|
||||
args.source.texture.pixelFormat == args.destination.texture.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
blitColor = false;
|
||||
}
|
||||
}
|
||||
id<MTLBlitCommandEncoder> const blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.texture
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.texture
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
if (blitDepth) {
|
||||
if (args.source.depth.sampleCount == args.destination.depth.sampleCount &&
|
||||
args.source.depth.pixelFormat == args.destination.depth.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.depth
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.depth
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
blitDepth = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetalBlitter::blitSlowPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
void MetalBlitter::blitSlowPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label) {
|
||||
|
||||
// scaling in any dimension is not allowed
|
||||
assert_invariant(args.source.region.size.depth == args.destination.region.size.depth);
|
||||
// we're always blitting a single plane
|
||||
uint32_t depthPlaneSource = args.source.region.origin.z;
|
||||
uint32_t depthPlaneDest = args.destination.region.origin.z;
|
||||
|
||||
assert_invariant(args.source.region.size.depth == args.destination.region.size.depth);
|
||||
uint32_t depthPlaneCount = args.source.region.size.depth;
|
||||
for (NSUInteger d = 0; d < depthPlaneCount; d++) {
|
||||
blitDepthPlane(cmdBuffer, blitColor, blitDepth, args, depthPlaneSource++, depthPlaneDest++,
|
||||
label);
|
||||
for (NSUInteger d = 0; d < args.source.region.size.depth; d++) {
|
||||
blitDepthPlane(cmdBuffer, args,
|
||||
depthPlaneSource++, depthPlaneDest++, label);
|
||||
}
|
||||
|
||||
blitColor = false;
|
||||
blitDepth = false;
|
||||
}
|
||||
|
||||
void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor, bool blitDepth,
|
||||
const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest,
|
||||
const char* label) {
|
||||
MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args,
|
||||
uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label) {
|
||||
|
||||
if (blitColor) {
|
||||
setupColorAttachment(args, descriptor, depthPlaneDest);
|
||||
}
|
||||
MTLRenderPassDescriptor* const descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
|
||||
if (blitDepth) {
|
||||
setupDepthAttachment(args, descriptor, depthPlaneDest);
|
||||
}
|
||||
setupAttachment(descriptor.colorAttachments[0], args, depthPlaneDest);
|
||||
|
||||
id<MTLRenderCommandEncoder> encoder = [cmdBuffer renderCommandEncoderWithDescriptor:descriptor];
|
||||
id<MTLRenderCommandEncoder> const encoder =
|
||||
[cmdBuffer renderCommandEncoderWithDescriptor:descriptor];
|
||||
encoder.label = @(label);
|
||||
|
||||
BlitFunctionKey key;
|
||||
key.blitColor = blitColor;
|
||||
key.blitDepth = blitDepth;
|
||||
key.msaaColorSource = blitColor && args.source.color.textureType == MTLTextureType2DMultisample;
|
||||
key.msaaDepthSource = blitDepth && args.source.depth.textureType == MTLTextureType2DMultisample;
|
||||
key.sources3D = blitColor && args.source.color.textureType == MTLTextureType3D;
|
||||
if (key.sources3D && blitDepth) {
|
||||
assert_invariant(args.source.depth.textureType == MTLTextureType3D);
|
||||
}
|
||||
id<MTLFunction> fragmentFunction = getBlitFragmentFunction(key);
|
||||
key.msaaColorSource = args.source.texture.textureType == MTLTextureType2DMultisample;
|
||||
key.sources3D = args.source.texture.textureType == MTLTextureType3D;
|
||||
id<MTLFunction> const fragmentFunction = getBlitFragmentFunction(key);
|
||||
|
||||
MetalPipelineState pipelineState {
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = getBlitVertexFunction(),
|
||||
.fragmentFunction = fragmentFunction,
|
||||
.vertexDescription = {},
|
||||
.colorAttachmentPixelFormat = {
|
||||
blitColor ? args.destination.color.pixelFormat : MTLPixelFormatInvalid,
|
||||
args.destination.texture.pixelFormat,
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid,
|
||||
@@ -319,31 +202,21 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid
|
||||
},
|
||||
.depthAttachmentPixelFormat =
|
||||
blitDepth ? args.destination.depth.pixelFormat : MTLPixelFormatInvalid,
|
||||
.depthAttachmentPixelFormat = MTLPixelFormatInvalid,
|
||||
.sampleCount = 1,
|
||||
.blendState = {}
|
||||
};
|
||||
id<MTLRenderPipelineState> pipeline = mContext.pipelineStateCache.getOrCreateState(pipelineState);
|
||||
id<MTLRenderPipelineState> const pipeline =
|
||||
mContext.pipelineStateCache.getOrCreateState(pipelineState);
|
||||
[encoder setRenderPipelineState:pipeline];
|
||||
|
||||
// For texture arrays, create a view of the texture at the given slice (layer).
|
||||
id<MTLTexture> srcTextureColor = args.source.color;
|
||||
|
||||
id<MTLTexture> srcTextureColor = args.source.texture;
|
||||
if (srcTextureColor && srcTextureColor.textureType == MTLTextureType2DArray) {
|
||||
srcTextureColor = createTextureViewWithSingleSlice(srcTextureColor, args.source.slice);
|
||||
}
|
||||
id<MTLTexture> srcTextureDepth = args.source.depth;
|
||||
if (srcTextureDepth && srcTextureDepth.textureType == MTLTextureType2DArray) {
|
||||
srcTextureDepth = createTextureViewWithSingleSlice(srcTextureDepth, args.source.slice);
|
||||
}
|
||||
|
||||
if (blitColor) {
|
||||
[encoder setFragmentTexture:srcTextureColor atIndex:0];
|
||||
}
|
||||
|
||||
if (blitDepth) {
|
||||
[encoder setFragmentTexture:srcTextureDepth atIndex:1];
|
||||
}
|
||||
[encoder setFragmentTexture:srcTextureColor atIndex:0];
|
||||
|
||||
SamplerMinFilter filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
|
||||
if (args.filter == SamplerMagFilter::NEAREST) {
|
||||
@@ -352,13 +225,13 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
|
||||
}
|
||||
|
||||
SamplerState s {
|
||||
SamplerState const s {
|
||||
.samplerParams = {
|
||||
.filterMag = args.filter,
|
||||
.filterMin = filterMin
|
||||
}
|
||||
};
|
||||
id<MTLSamplerState> sampler = mContext.samplerStateCache.getOrCreateState(s);
|
||||
id<MTLSamplerState> const sampler = mContext.samplerStateCache.getOrCreateState(s);
|
||||
[encoder setFragmentSamplerState:sampler atIndex:0];
|
||||
|
||||
MTLViewport viewport;
|
||||
@@ -370,11 +243,11 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
viewport.zfar = 1.0;
|
||||
[encoder setViewport:viewport];
|
||||
|
||||
DepthStencilState depthStencilState {
|
||||
DepthStencilState const depthStencilState {
|
||||
.depthCompare = MTLCompareFunctionAlways,
|
||||
.depthWriteEnabled = blitDepth
|
||||
.depthWriteEnabled = false
|
||||
};
|
||||
id<MTLDepthStencilState> depthStencil =
|
||||
id<MTLDepthStencilState> const depthStencil =
|
||||
mContext.depthStencilStateCache.getOrCreateState(depthStencilState);
|
||||
[encoder setDepthStencilState:depthStencil];
|
||||
|
||||
@@ -410,80 +283,42 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
[encoder endEncoding];
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalBlitter::createIntermediateTexture(id<MTLTexture> t, MTLSize size) {
|
||||
MTLTextureDescriptor* descriptor = [MTLTextureDescriptor new];
|
||||
descriptor.textureType = size.depth == 1 ? MTLTextureType2D : MTLTextureType3D;
|
||||
descriptor.pixelFormat = t.pixelFormat;
|
||||
descriptor.width = size.width;
|
||||
descriptor.height = size.height;
|
||||
descriptor.depth = size.depth;
|
||||
descriptor.usage = t.usage & MTLTextureUsageRenderTarget;
|
||||
return [mContext.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
void MetalBlitter::shutdown() noexcept {
|
||||
mBlitFunctions.clear();
|
||||
mVertexFunction = nil;
|
||||
}
|
||||
|
||||
void MetalBlitter::setupColorAttachment(const BlitArgs& args,
|
||||
MTLRenderPassDescriptor* descriptor, uint32_t depthPlane) {
|
||||
descriptor.colorAttachments[0].texture = args.destination.color;
|
||||
descriptor.colorAttachments[0].level = args.destination.level;
|
||||
descriptor.colorAttachments[0].slice = args.destination.slice;
|
||||
descriptor.colorAttachments[0].depthPlane = depthPlane;
|
||||
|
||||
descriptor.colorAttachments[0].loadAction = MTLLoadActionLoad;
|
||||
void MetalBlitter::setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor,
|
||||
const BlitArgs& args, uint32_t depthPlane) {
|
||||
descriptor.texture = args.destination.texture;
|
||||
descriptor.level = args.destination.level;
|
||||
descriptor.slice = args.destination.slice;
|
||||
descriptor.depthPlane = depthPlane;
|
||||
descriptor.loadAction = MTLLoadActionLoad;
|
||||
// We don't need to load the contents of the attachment if we're blitting over all of it.
|
||||
if (args.colorDestinationIsFullAttachment()) {
|
||||
descriptor.colorAttachments[0].loadAction = MTLLoadActionDontCare;
|
||||
if (args.destinationIsFullAttachment()) {
|
||||
descriptor.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
descriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
descriptor.storeAction = MTLStoreActionStore;
|
||||
}
|
||||
|
||||
void MetalBlitter::setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane) {
|
||||
descriptor.depthAttachment.texture = args.destination.depth;
|
||||
descriptor.depthAttachment.level = args.destination.level;
|
||||
descriptor.depthAttachment.slice = args.destination.slice;
|
||||
descriptor.depthAttachment.depthPlane = depthPlane;
|
||||
|
||||
descriptor.depthAttachment.loadAction = MTLLoadActionLoad;
|
||||
// We don't need to load the contents of the attachment if we're blitting over all of it.
|
||||
if (args.depthDestinationIsFullAttachment()) {
|
||||
descriptor.depthAttachment.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
descriptor.depthAttachment.storeAction = MTLStoreActionStore;
|
||||
}
|
||||
|
||||
id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) {
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
NSMutableDictionary* macros = [NSMutableDictionary dictionary];
|
||||
if (key.blitColor) {
|
||||
macros[@"BLIT_COLOR"] = @"1";
|
||||
}
|
||||
if (key.blitDepth) {
|
||||
macros[@"BLIT_DEPTH"] = @"1";
|
||||
}
|
||||
id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) const {
|
||||
MTLCompileOptions* const options = [MTLCompileOptions new];
|
||||
NSMutableDictionary* const macros = [NSMutableDictionary dictionary];
|
||||
if (key.msaaColorSource) {
|
||||
macros[@"MSAA_COLOR_SOURCE"] = @"1";
|
||||
}
|
||||
if (key.msaaDepthSource) {
|
||||
macros[@"MSAA_DEPTH_SOURCE"] = @"1";
|
||||
}
|
||||
if (key.sources3D) {
|
||||
macros[@"SOURCES_3D"] = @"1";
|
||||
}
|
||||
options.preprocessorMacros = macros;
|
||||
NSString* objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSString* const objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"blitterFrag"];
|
||||
id <MTLLibrary> const library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
id <MTLFunction> const function = [library newFunctionWithName:@"blitterFrag"];
|
||||
|
||||
if (!library || !function) {
|
||||
if (error) {
|
||||
@@ -501,13 +336,14 @@ id<MTLFunction> MetalBlitter::getBlitVertexFunction() {
|
||||
return mVertexFunction;
|
||||
}
|
||||
|
||||
NSString* objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSString* const objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"blitterVertex"];
|
||||
id <MTLLibrary> const library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
|
||||
id<MTLFunction> const function = [library newFunctionWithName:@"blitterVertex"];
|
||||
|
||||
if (!library || !function) {
|
||||
if (error) {
|
||||
@@ -535,5 +371,5 @@ id<MTLFunction> MetalBlitter::getBlitFragmentFunction(BlitFunctionKey key) {
|
||||
return function;
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -73,7 +73,10 @@ private:
|
||||
std::unordered_set<MetalBufferPoolEntry const*> mUsedStages;
|
||||
|
||||
// Store the current "time" (really just a frame count) and LRU eviction parameters.
|
||||
uint64_t mCurrentFrame = 0;
|
||||
// An atomic is necessary as mCurrentFrame is incremented in gc() (called on
|
||||
// the driver thread) and read from acquireBuffer() and releaseBuffer(),
|
||||
// which may be called on non-driver threads.
|
||||
std::atomic<uint64_t> mCurrentFrame = 0;
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = 10;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -128,7 +142,10 @@ struct MetalContext {
|
||||
// Fences, only supported on macOS 10.14 and iOS 12 and above.
|
||||
API_AVAILABLE(macos(10.14), ios(12.0))
|
||||
MTLSharedEventListener* eventListener = nil;
|
||||
uint64_t signalId = 1;
|
||||
// signalId is incremented in the MetalFence constructor, which is called on
|
||||
// both the driver (MetalTimerQueryFence::beginTimeElapsedQuery) and main
|
||||
// threads (in createFenceS), so an atomic is necessary.
|
||||
std::atomic<uint64_t> signalId = 1;
|
||||
|
||||
MetalTimerQueryInterface* timerQueryImpl;
|
||||
|
||||
@@ -136,6 +153,8 @@ struct MetalContext {
|
||||
|
||||
MTLViewport currentViewport;
|
||||
|
||||
MetalShaderCompiler* shaderCompiler = nullptr;
|
||||
|
||||
#if defined(FILAMENT_METAL_PROFILING)
|
||||
// Logging and profiling.
|
||||
os_log_t log;
|
||||
|
||||
@@ -28,17 +28,21 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
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
|
||||
@@ -52,13 +56,13 @@ class MetalDriver final : public DriverBase {
|
||||
|
||||
public:
|
||||
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
|
||||
void runAtNextTick(const std::function<void()>& fn) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
friend class MetalSwapChain;
|
||||
|
||||
MetalPlatform& mPlatform;
|
||||
|
||||
MetalContext* mContext;
|
||||
|
||||
ShaderModel getShaderModel() const noexcept final;
|
||||
@@ -66,6 +70,13 @@ private:
|
||||
// Overrides the default implementation by wrapping the call to fn in an @autoreleasepool block.
|
||||
void execute(std::function<void(void)> const& fn) noexcept final;
|
||||
|
||||
/*
|
||||
* Tasks run regularly on the driver thread.
|
||||
*/
|
||||
void executeTickOps() noexcept;
|
||||
std::vector<std::function<void()>> mTickOps;
|
||||
std::mutex mTickOpsLock;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
*/
|
||||
|
||||
@@ -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,10 +161,12 @@ MetalDriver::~MetalDriver() noexcept {
|
||||
delete mContext->bufferPool;
|
||||
delete mContext->blitter;
|
||||
delete mContext->timerQueryImpl;
|
||||
delete mContext->shaderCompiler;
|
||||
delete mContext;
|
||||
}
|
||||
|
||||
void MetalDriver::tick(int) {
|
||||
executeTickOps();
|
||||
}
|
||||
|
||||
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
@@ -303,8 +309,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 +324,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 +537,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,15 +574,24 @@ 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();
|
||||
|
||||
executeTickOps();
|
||||
|
||||
mContext->bufferPool->reset();
|
||||
mContext->commandQueue = nil;
|
||||
|
||||
MetalExternalImage::shutdown(*mContext);
|
||||
mContext->blitter->shutdown();
|
||||
mContext->shaderCompiler->terminate();
|
||||
}
|
||||
|
||||
ShaderModel MetalDriver::getShaderModel() const noexcept {
|
||||
@@ -701,6 +727,10 @@ bool MetalDriver::isStereoSupported() {
|
||||
}
|
||||
|
||||
bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -842,10 +872,6 @@ void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
tex->generateMipmaps();
|
||||
}
|
||||
|
||||
bool MetalDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescriptor&& data) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"updateSamplerGroup must be called outside of a render pass.");
|
||||
@@ -854,13 +880,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 +898,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 +965,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1229,14 +1259,14 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
||||
textureDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget;
|
||||
id<MTLTexture> readPixelsTexture = [mContext->device newTextureWithDescriptor:textureDescriptor];
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.level = miplevel;
|
||||
args.source.region = MTLRegionMake2D(0, 0, srcTexture.width >> miplevel, srcTexture.height >> miplevel);
|
||||
args.source.texture = srcTexture;
|
||||
args.destination.level = 0;
|
||||
args.destination.region = MTLRegionMake2D(0, 0, readPixelsTexture.width, readPixelsTexture.height);
|
||||
args.source.color = srcTexture;
|
||||
args.destination.color = readPixelsTexture;
|
||||
args.destination.texture = readPixelsTexture;
|
||||
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "readPixels blit");
|
||||
|
||||
@@ -1272,24 +1302,123 @@ void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
void MetalDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
auto* const srcTexture = handle_cast<MetalTexture>(src);
|
||||
auto* const dstTexture = handle_cast<MetalTexture>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"resolve() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+srcTexture->samples, +dstTexture->samples);
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format),
|
||||
"can't resolve depth formats");
|
||||
|
||||
ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format),
|
||||
"can't resolve stencil formats");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
// FIXME: on metal the blit() call below always take the slow path (using a shader)
|
||||
|
||||
blit( dst, dstLevel, dstLayer, {},
|
||||
src, srcLevel, srcLayer, {},
|
||||
{ dstTexture->width, dstTexture->height });
|
||||
}
|
||||
|
||||
void MetalDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
|
||||
|
||||
auto isBlitableTextureType = [](MTLTextureType t) -> bool {
|
||||
return t == MTLTextureType2D || t == MTLTextureType2DMultisample ||
|
||||
t == MTLTextureType2DArray;
|
||||
};
|
||||
|
||||
auto* const srcTexture = handle_cast<MetalTexture>(src);
|
||||
auto* const dstTexture = handle_cast<MetalTexture>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blit() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcTexture->getMtlTextureForRead().textureType) &&
|
||||
isBlitableTextureType(dstTexture->getMtlTextureForWrite().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.region = MTLRegionMake2D(
|
||||
(NSUInteger)srcOrigin.x,
|
||||
std::max(srcTexture->height - (int64_t)srcOrigin.y - size.y, (int64_t)0),
|
||||
size.x, size.y);
|
||||
args.destination.region = MTLRegionMake2D(
|
||||
(NSUInteger)dstOrigin.x,
|
||||
std::max(dstTexture->height - (int64_t)dstOrigin.y - size.y, (int64_t)0),
|
||||
size.x, size.y);
|
||||
|
||||
// FIXME: we shouldn't need to know the type here. This is an artifact of using the old API.
|
||||
|
||||
args.source.texture = srcTexture->getMtlTextureForRead();
|
||||
args.destination.texture = dstTexture->getMtlTextureForWrite();
|
||||
args.source.level = srcLevel;
|
||||
args.source.slice = srcLayer;
|
||||
args.destination.level = dstLevel;
|
||||
args.destination.slice = dstLayer;
|
||||
|
||||
// TODO: The blit() call below always take the fast path.
|
||||
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blit/resolve");
|
||||
|
||||
dstTexture->extendLodRangeTo(dstLevel);
|
||||
}
|
||||
|
||||
void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
// If we're the in middle of a render pass, finish it.
|
||||
// This condition should only occur during copyFrame. It's okay to end the render pass because
|
||||
// we don't issue any other rendering commands.
|
||||
if (mContext->currentRenderPassEncoder) {
|
||||
[mContext->currentRenderPassEncoder endEncoding];
|
||||
mContext->currentRenderPassEncoder = nil;
|
||||
}
|
||||
|
||||
// Note: blitDEPRECATED is only used by Renderer::copyFrame
|
||||
// It is called between beginFrame and endFrame, but should never be called in the middle of
|
||||
// a render pass.
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blitDEPRECATED() cannot be invoked inside a render pass.");
|
||||
|
||||
auto srcTarget = handle_cast<MetalRenderTarget>(src);
|
||||
auto dstTarget = handle_cast<MetalRenderTarget>(dst);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
!(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)),
|
||||
"Blitting only supports COLOR0");
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
@@ -1300,54 +1429,28 @@ void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
t == MTLTextureType2DArray;
|
||||
};
|
||||
|
||||
// MetalBlitter supports blitting color and depth simultaneously, but for simplicitly we'll blit
|
||||
// them separately. In practice, Filament only ever blits a single buffer at a time anyway.
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
|
||||
if (any(buffers & TargetBufferFlags::COLOR_ALL)) {
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalRenderTarget::Attachment dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.source.texture = srcColorAttachment.getTexture();
|
||||
args.source.level = srcColorAttachment.level;
|
||||
args.source.slice = srcColorAttachment.layer;
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.source.color = srcColorAttachment.getTexture();
|
||||
args.destination.color = dstColorAttachment.getTexture();
|
||||
args.source.level = srcColorAttachment.level;
|
||||
args.destination.level = dstColorAttachment.level;
|
||||
args.source.slice = srcColorAttachment.layer;
|
||||
args.destination.slice = dstColorAttachment.layer;
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Color blit");
|
||||
}
|
||||
}
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.destination.texture = dstColorAttachment.getTexture();
|
||||
args.destination.level = dstColorAttachment.level;
|
||||
args.destination.slice = dstColorAttachment.layer;
|
||||
|
||||
if (any(buffers & TargetBufferFlags::DEPTH)) {
|
||||
MetalRenderTarget::Attachment srcDepthAttachment = srcTarget->getDepthAttachment();
|
||||
MetalRenderTarget::Attachment dstDepthAttachment = dstTarget->getDepthAttachment();
|
||||
|
||||
if (srcDepthAttachment && dstDepthAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcDepthAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstDepthAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.source.depth = srcDepthAttachment.getTexture();
|
||||
args.destination.depth = dstDepthAttachment.getTexture();
|
||||
args.source.level = srcDepthAttachment.level;
|
||||
args.destination.level = dstDepthAttachment.level;
|
||||
args.source.slice = srcDepthAttachment.layer;
|
||||
args.destination.slice = dstDepthAttachment.layer;
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Depth blit");
|
||||
}
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1357,23 +1460,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 +1554,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 };
|
||||
@@ -1476,9 +1588,9 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
stencilPixelFormat = stencilAttachment.getPixelFormat();
|
||||
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
|
||||
}
|
||||
MetalPipelineState pipelineState {
|
||||
.vertexFunction = program->vertexFunction,
|
||||
.fragmentFunction = program->fragmentFunction,
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = vertex,
|
||||
.fragmentFunction = fragment,
|
||||
.vertexDescription = primitive->vertexDescription,
|
||||
.colorAttachmentPixelFormat = {
|
||||
colorPixelFormat[0],
|
||||
@@ -1494,13 +1606,13 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
.stencilAttachmentPixelFormat = stencilPixelFormat,
|
||||
.sampleCount = mContext->currentRenderTarget->getSamples(),
|
||||
.blendState = BlendState {
|
||||
.blendingEnabled = rs.hasBlending(),
|
||||
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
|
||||
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
|
||||
.sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB),
|
||||
.sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha),
|
||||
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
|
||||
.destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha),
|
||||
.destinationRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionDstRGB),
|
||||
.destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha)
|
||||
.sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha),
|
||||
.sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB),
|
||||
.blendingEnabled = rs.hasBlending(),
|
||||
},
|
||||
.colorWrite = rs.colorWrite
|
||||
};
|
||||
@@ -1623,7 +1735,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 +1762,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 +1805,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];
|
||||
@@ -1797,6 +1911,21 @@ void MetalDriver::enumerateBoundBuffers(BufferObjectBinding bindingType,
|
||||
void MetalDriver::resetState(int) {
|
||||
}
|
||||
|
||||
void MetalDriver::runAtNextTick(const std::function<void()>& fn) noexcept {
|
||||
std::lock_guard<std::mutex> const lock(mTickOpsLock);
|
||||
mTickOps.push_back(fn);
|
||||
}
|
||||
|
||||
void MetalDriver::executeTickOps() noexcept {
|
||||
std::vector<std::function<void()>> ops;
|
||||
mTickOpsLock.lock();
|
||||
std::swap(ops, mTickOps);
|
||||
mTickOpsLock.unlock();
|
||||
for (const auto& f : ops) {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
// explicit instantiation of the Dispatcher
|
||||
template class ConcreteDispatcher<MetalDriver>;
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -39,7 +39,15 @@ namespace filament {
|
||||
namespace backend {
|
||||
|
||||
static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
NSUInteger u = 0;
|
||||
NSUInteger u = MTLTextureUsageUnknown;
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
}
|
||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
// This is only needed because of the slowpath is MetalBlitter
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
@@ -49,16 +57,23 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
|
||||
// All textures can be blitted from, so they must have the UsageShaderRead flag.
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
if (any(usage & TextureUsage::BLIT_DST)) {
|
||||
// This is only needed because of the slowpath is MetalBlitter
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
}
|
||||
|
||||
return MTLTextureUsage(u);
|
||||
}
|
||||
|
||||
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 +94,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 +184,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) {
|
||||
@@ -214,13 +245,30 @@ void MetalSwapChain::present() {
|
||||
}
|
||||
}
|
||||
|
||||
struct PresentDrawableData {
|
||||
void* drawable = nullptr;
|
||||
MetalDriver* driver = nullptr;
|
||||
};
|
||||
|
||||
void presentDrawable(bool presentFrame, void* user) {
|
||||
// CFBridgingRelease here is used to balance the CFBridgingRetain inside of acquireDrawable.
|
||||
id<CAMetalDrawable> drawable = (id<CAMetalDrawable>) CFBridgingRelease(user);
|
||||
auto* presentDrawableData = static_cast<PresentDrawableData*>(user);
|
||||
|
||||
// CFBridgingRelease here is used to balance the CFBridgingRetain inside acquireDrawable.
|
||||
id<CAMetalDrawable> drawable =
|
||||
(id<CAMetalDrawable>)CFBridgingRelease(presentDrawableData->drawable);
|
||||
if (presentFrame) {
|
||||
[drawable present];
|
||||
}
|
||||
// The drawable will be released here when the "drawable" variable goes out of scope.
|
||||
|
||||
// Schedule the drawable destruction on the driver thread.
|
||||
void* voidDrawable = (void*) CFBridgingRetain(drawable);
|
||||
MetalDriver* driver = presentDrawableData->driver;
|
||||
driver->runAtNextTick([voidDrawable]() {
|
||||
// The drawable is released here.
|
||||
CFBridgingRelease(voidDrawable);
|
||||
});
|
||||
|
||||
delete presentDrawableData;
|
||||
}
|
||||
|
||||
void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
@@ -235,13 +283,15 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
// capture the _this_ pointer (MetalSwapChain*) instead of the drawable.
|
||||
id<CAMetalDrawable> d = drawable;
|
||||
void* userData = frameScheduledUserData;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
// CFBridgingRetain is used here to give the drawable a +1 retain count before
|
||||
// casting it to a void*.
|
||||
PresentCallable callable(presentDrawable, (void*) CFBridgingRetain(d));
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
callback(callable, userData);
|
||||
});
|
||||
auto* presentDrawableData = new PresentDrawableData;
|
||||
presentDrawableData->drawable = (void*) CFBridgingRetain(d);
|
||||
presentDrawableData->driver = driver;
|
||||
PresentCallable callable(presentDrawable, (void*) presentDrawableData);
|
||||
callback(callable, userData);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -280,6 +330,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 +340,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 +545,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);
|
||||
}
|
||||
@@ -749,6 +787,7 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
#endif
|
||||
|
||||
id<MTLTexture> stagingTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
// FIXME? Why is this not just `MTLRegion sourceRegion = region;` ?
|
||||
MTLRegion sourceRegion = MTLRegionMake3D(0, 0, 0,
|
||||
region.size.width, region.size.height, region.size.depth);
|
||||
[stagingTexture replaceRegion:sourceRegion
|
||||
@@ -775,27 +814,27 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
slices:NSMakeRange(0, slices)];
|
||||
}
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.level = 0;
|
||||
args.source.slice = 0;
|
||||
args.source.region = sourceRegion;
|
||||
args.source.texture = stagingTexture;
|
||||
args.destination.level = level;
|
||||
args.destination.slice = slice;
|
||||
args.destination.region = region;
|
||||
args.source.color = stagingTexture;
|
||||
args.destination.color = destinationTexture;
|
||||
args.destination.texture = destinationTexture;
|
||||
context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit");
|
||||
}
|
||||
|
||||
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 +1162,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,228 @@
|
||||
/*
|
||||
* 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];
|
||||
|
||||
// By default, Metal uses the most recent language version.
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
|
||||
// Disable Fast Math optimizations.
|
||||
// This ensures that operations adhere to IEEE standards for floating-point arithmetic,
|
||||
// which is crucial for half precision floats in scenarios where fast math optimizations
|
||||
// lead to inaccuracies, such as in handling special values like NaN or Infinity.
|
||||
options.fastMathEnabled = NO;
|
||||
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
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]() {
|
||||
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
|
||||
@@ -182,6 +182,10 @@ bool NoopDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
@@ -252,10 +256,6 @@ void NoopDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
||||
|
||||
void NoopDriver::generateMipmaps(Handle<HwTexture> th) { }
|
||||
|
||||
bool NoopDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void NoopDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
BufferDescriptor&& data) {
|
||||
scheduleDestroy(std::move(data));
|
||||
@@ -322,12 +322,23 @@ void NoopDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void NoopDriver::blit(TargetBufferFlags buffers,
|
||||
void NoopDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
}
|
||||
|
||||
void NoopDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
}
|
||||
|
||||
void NoopDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
}
|
||||
|
||||
void NoopDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t instanceCount) {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -255,6 +274,7 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
||||
#endif
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
if (ext.EXT_clip_control) {
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
||||
@@ -262,9 +282,11 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ext.EXT_clip_cull_distance) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
glEnable(GL_CLIP_DISTANCE1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +311,9 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
# ifdef BACKEND_OPENGL_VERSION_GL
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
|
||||
# elif defined(GL_EXT_disjoint_timer_query)
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# ifndef __EMSCRIPTEN__
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# endif
|
||||
# endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
// core in ES 3.0 and GL 4.3
|
||||
@@ -301,6 +325,7 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
# ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
# ifndef __EMSCRIPTEN__
|
||||
if (UTILS_UNLIKELY(major == 2)) {
|
||||
// Runtime OpenGL version is ES 2.x
|
||||
if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
|
||||
@@ -328,6 +353,7 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
|
||||
}
|
||||
# endif // __EMSCRIPTEN__
|
||||
# endif // IOS
|
||||
#else
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
|
||||
@@ -456,7 +482,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
// (that should be regardless of ANGLE, but we should double-check)
|
||||
bugs->split_easu = true;
|
||||
}
|
||||
// TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
|
||||
}
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
@@ -561,17 +586,23 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
// figure out and initialize the extensions we need
|
||||
using namespace std::literals;
|
||||
ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
|
||||
#endif
|
||||
ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
|
||||
ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
|
||||
ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
#endif
|
||||
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
|
||||
ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
|
||||
#endif
|
||||
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_texture_compression_etc2 = true;
|
||||
#endif
|
||||
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -172,7 +172,7 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
||||
mShaderCompilerService(*this),
|
||||
mHandleAllocator("Handles", driverConfig.handleArenaSize),
|
||||
mSamplerMap(32) {
|
||||
|
||||
|
||||
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
|
||||
|
||||
// set a reasonable default value for our stream array
|
||||
@@ -262,21 +262,27 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.targetIndex);
|
||||
}
|
||||
|
||||
void OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
if (UTILS_UNLIKELY(!p->isValid())) {
|
||||
// If the program is not valid, we can't call use().
|
||||
return false;
|
||||
}
|
||||
|
||||
// set-up textures and samplers in the proper TMUs (as specified in setSamplers)
|
||||
p->use(this, mContext);
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
|
||||
auto [buffer, age] = mUniformBindings[i];
|
||||
auto [id, buffer, age] = mUniformBindings[i];
|
||||
if (buffer) {
|
||||
p->updateUniforms(i, buffer, age);
|
||||
p->updateUniforms(i, id, buffer, age);
|
||||
}
|
||||
}
|
||||
// Set the output colorspace for this program (linear or rec709). This in only relevant
|
||||
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
|
||||
p->setRec709ColorSpace(mRec709OutputColorspace);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -479,6 +485,7 @@ void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh,
|
||||
|
||||
GLBufferObject* bo = construct<GLBufferObject>(boh, byteCount, bindingType, usage);
|
||||
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
bo->gl.id = ++mLastAssignedEmulatedUboId;
|
||||
bo->gl.buffer = malloc(byteCount);
|
||||
memset(bo->gl.buffer, 0, byteCount);
|
||||
} else {
|
||||
@@ -530,7 +537,7 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
// Here we patch the specification constants to enable or not the rec709 output
|
||||
// Here we patch the specialization constants to enable or not the rec709 output
|
||||
// color space emulation in this program. Obviously, the backend shouldn't know about
|
||||
// specific spec-constants, so we need to handle failures gracefully. This cannot be
|
||||
// done at Material creation time because only the backend has access to
|
||||
@@ -555,7 +562,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);
|
||||
@@ -594,8 +602,9 @@ void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
|
||||
}
|
||||
} else {
|
||||
glTexImage2D(t->gl.target, level, GLint(t->gl.internalFormat),
|
||||
GLsizei(width), GLsizei(height), 0,
|
||||
format, type, nullptr);
|
||||
std::max(GLsizei(1), GLsizei(width >> level)),
|
||||
std::max(GLsizei(1), GLsizei(height >> level)),
|
||||
0, format, type, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1055,6 +1064,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
} else
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_multisampled_render_to_texture
|
||||
// EXT_multisampled_render_to_texture only support GL_COLOR_ATTACHMENT0
|
||||
if (!attachmentTypeNotSupportedByMSRTT && (t->depth <= 1)
|
||||
@@ -1067,7 +1077,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
// This extension only exists on OpenGL ES.
|
||||
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
|
||||
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glext::glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
|
||||
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
|
||||
attachment, target, t->gl.id, binfo.level, rt->gl.samples);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment,
|
||||
@@ -1075,7 +1085,8 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
} else
|
||||
#endif
|
||||
#endif // GL_EXT_multisampled_render_to_texture
|
||||
#endif // __EMSCRIPTEN__
|
||||
if (!any(t->usage & TextureUsage::SAMPLEABLE) && t->samples > 1) {
|
||||
assert_invariant(rt->gl.samples > 1);
|
||||
assert_invariant(glIsRenderbuffer(t->gl.id));
|
||||
@@ -1174,6 +1185,7 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32
|
||||
uint32_t height, uint8_t samples) const noexcept {
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
|
||||
if (samples > 1) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_multisampled_render_to_texture
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_multisampled_render_to_texture ||
|
||||
@@ -1181,7 +1193,8 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32
|
||||
glext::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER,
|
||||
samples, internalformat, width, height);
|
||||
} else
|
||||
#endif
|
||||
#endif // GL_EXT_multisampled_render_to_texture
|
||||
#endif // __EMSCRIPTEN__
|
||||
{
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
|
||||
@@ -1289,7 +1302,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 +1465,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 +1478,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 +1498,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 +1796,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:
|
||||
@@ -1884,6 +1910,10 @@ bool OpenGLDriver::isParallelShaderCompileSupported() {
|
||||
return mShaderCompilerService.isParallelShaderCompileSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -1953,6 +1983,13 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
|
||||
GLSwapChain* scRead = handle_cast<GLSwapChain*>(schRead);
|
||||
mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain);
|
||||
mCurrentDrawSwapChain = scDraw;
|
||||
|
||||
// From the GL spec for glViewport and glScissor:
|
||||
// When a GL context is first attached to a window, width and height are set to the
|
||||
// dimensions of that window.
|
||||
// So basically, our viewport/scissor can be reset to "something" here.
|
||||
mContext.state.window.viewport = {};
|
||||
mContext.state.window.scissor = {};
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -2198,8 +2235,7 @@ void OpenGLDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint
|
||||
t->gl.baseLevel = (int8_t)minLevel;
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
|
||||
|
||||
t->gl
|
||||
.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
|
||||
t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
|
||||
}
|
||||
#endif
|
||||
@@ -2249,16 +2285,13 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
bool OpenGLDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
PixelBufferDescriptor&& p) {
|
||||
auto& gl = mContext;
|
||||
|
||||
assert_invariant(t != nullptr);
|
||||
assert_invariant(xoffset + width <= std::max(1u, t->width >> level));
|
||||
assert_invariant(yoffset + height <= std::max(1u, t->height >> level));
|
||||
assert_invariant(t->samples <= 1);
|
||||
@@ -2268,8 +2301,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()) {
|
||||
@@ -2879,7 +2920,11 @@ void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind
|
||||
assert_invariant(offset + size <= ub->byteCount);
|
||||
|
||||
if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
|
||||
mUniformBindings[index] = { static_cast<uint8_t const*>(ub->gl.buffer) + offset, ub->age };
|
||||
mUniformBindings[index] = {
|
||||
ub->gl.id,
|
||||
static_cast<uint8_t const*>(ub->gl.buffer) + offset,
|
||||
ub->age,
|
||||
};
|
||||
} else {
|
||||
GLenum const target = GLUtils::getBufferBindingType(bindingType);
|
||||
|
||||
@@ -2945,16 +2990,18 @@ GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
#endif
|
||||
|
||||
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_debug_marker) {
|
||||
glInsertEventMarkerEXT(GLsizei(len ? len : strlen(string)), string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
@@ -2967,9 +3014,11 @@ void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::popGroupMarker(int) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
@@ -2982,6 +3031,7 @@ void OpenGLDriver::popGroupMarker(int) {
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::startCapture(int) {
|
||||
@@ -3397,75 +3447,230 @@ void OpenGLDriver::clearWithRasterPipe(TargetBufferFlags clearFlags,
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::blit(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
void OpenGLDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
DEBUG_MARKER()
|
||||
GLTexture const* const s = handle_cast<GLTexture*>(src);
|
||||
GLTexture const* const d = handle_cast<GLTexture*>(dst);
|
||||
assert_invariant(s);
|
||||
assert_invariant(d);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
d->width == s->width && d->height == s->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(s->samples > 1 && d->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+s->samples, +d->samples);
|
||||
|
||||
blit( dst, dstLevel, dstLayer, {},
|
||||
src, srcLevel, srcLayer, {},
|
||||
{ d->width, d->height });
|
||||
}
|
||||
|
||||
void OpenGLDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, uint2 srcOrigin,
|
||||
uint2 size) {
|
||||
DEBUG_MARKER()
|
||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||
assert_invariant(!gl.isES2());
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLbitfield const mask = getAttachmentBitfield(buffers);
|
||||
if (mask) {
|
||||
GLenum glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR;
|
||||
if (mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) {
|
||||
// GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or
|
||||
// GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.
|
||||
glFilterMode = GL_NEAREST;
|
||||
}
|
||||
|
||||
// note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar
|
||||
// buffer -- this should produce the same output that if we copied from the resolved
|
||||
// texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this
|
||||
// is an emulation of that. We cannot use the resolved texture easily because it's not
|
||||
// actually attached to the RenderTarget. Another implementation would be to do a
|
||||
// reverse-resolve, but that wouldn't buy us anything.
|
||||
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
||||
GLRenderTarget const* d = handle_cast<GLRenderTarget const*>(dst);
|
||||
GLTexture* d = handle_cast<GLTexture*>(dst);
|
||||
GLTexture* s = handle_cast<GLTexture*>(src);
|
||||
assert_invariant(d);
|
||||
assert_invariant(s);
|
||||
|
||||
// blit operations are only supported from RenderTargets that have only COLOR0 (or
|
||||
// no color buffer at all)
|
||||
assert_invariant(
|
||||
!(s->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
ASSERT_PRECONDITION_NON_FATAL(any(d->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
assert_invariant(
|
||||
!(d->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
ASSERT_PRECONDITION_NON_FATAL(any(s->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
// With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS
|
||||
// for the draw buffer is greater than zero. This works with OpenGL, so we want to
|
||||
// make sure to catch this scenario.
|
||||
assert_invariant(d->gl.samples <= 1);
|
||||
ASSERT_PRECONDITION_NON_FATAL(s->format == d->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and the formats of draw and read buffers are not identical.
|
||||
// However, it's not well-defined in the spec what "format" means. So it's difficult
|
||||
// to have an assert here -- especially when dealing with the default framebuffer
|
||||
enum class AttachmentType : GLenum {
|
||||
COLOR = GL_COLOR_ATTACHMENT0,
|
||||
DEPTH = GL_DEPTH_ATTACHMENT,
|
||||
STENCIL = GL_STENCIL_ATTACHMENT,
|
||||
DEPTH_STENCIL = GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
};
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and (...) the source and destination rectangles are not defined with the
|
||||
// same (X0, Y0) and (X1, Y1) bounds.
|
||||
auto getFormatType = [](TextureFormat format) -> AttachmentType {
|
||||
bool const depth = isDepthFormat(format);
|
||||
bool const stencil = isStencilFormat(format);
|
||||
if (depth && stencil) return AttachmentType::DEPTH_STENCIL;
|
||||
if (depth) return AttachmentType::DEPTH;
|
||||
if (stencil) return AttachmentType::STENCIL;
|
||||
return AttachmentType::COLOR;
|
||||
};
|
||||
|
||||
// Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what
|
||||
// happens when blitting from an "implicit" resolve render target (does it work?), so
|
||||
// to ere on the safe side, we don't allow it.
|
||||
if (s->gl.samples > 1) {
|
||||
assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect)));
|
||||
}
|
||||
AttachmentType const type = getFormatType(d->format);
|
||||
assert_invariant(type == getFormatType(s->format));
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
|
||||
// GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or
|
||||
// GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.
|
||||
GLbitfield mask = {};
|
||||
switch (type) {
|
||||
case AttachmentType::COLOR:
|
||||
mask = GL_COLOR_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::DEPTH:
|
||||
mask = GL_DEPTH_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::STENCIL:
|
||||
mask = GL_STENCIL_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::DEPTH_STENCIL:
|
||||
mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
break;
|
||||
};
|
||||
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
GLuint fbo[2] = {};
|
||||
glGenFramebuffers(2, fbo);
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
|
||||
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
|
||||
mask, glFilterMode);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[0]);
|
||||
switch (d->target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_2D, d->gl.id, dstLevel);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_RENDERBUFFER, d->gl.id);
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + dstLayer, d->gl.id, dstLevel);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
case SamplerType::SAMPLER_3D:
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
d->gl.id, dstLevel, dstLayer);
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
|
||||
switch (s->target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
if (any(s->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_2D, s->gl.id, srcLevel);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_RENDERBUFFER, s->gl.id);
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + srcLayer, s->gl.id, srcLevel);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
case SamplerType::SAMPLER_3D:
|
||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
s->gl.id, srcLevel, srcLayer);
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcOrigin.x, srcOrigin.y, srcOrigin.x + size.x, srcOrigin.y + size.y,
|
||||
dstOrigin.x, dstOrigin.y, dstOrigin.x + size.x, dstOrigin.y + size.y,
|
||||
mask, GL_NEAREST);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
|
||||
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
|
||||
// In a sense, blitting to a texture level is similar to calling setTextureData on it; in
|
||||
// both cases, we update the base/max LOD to give shaders access to levels as they become
|
||||
// available. Note that this can only expand the LOD range (never shrink it), and that
|
||||
// users can override this range by calling setMinMaxLevels().
|
||||
updateTextureLodRange(d, int8_t(dstLevel));
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
|
||||
// Note: blitDEPRECATED is only used by Renderer::copyFrame
|
||||
|
||||
DEBUG_MARKER()
|
||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||
assert_invariant(!gl.isES2());
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
|
||||
GLenum const glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR;
|
||||
|
||||
// note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar
|
||||
// buffer -- this should produce the same output that if we copied from the resolved
|
||||
// texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this
|
||||
// is an emulation of that. We cannot use the resolved texture easily because it's not
|
||||
// actually attached to the RenderTarget. Another implementation would be to do a
|
||||
// reverse-resolve, but that wouldn't buy us anything.
|
||||
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
||||
GLRenderTarget const* d = handle_cast<GLRenderTarget const*>(dst);
|
||||
|
||||
// With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS
|
||||
// for the draw buffer is greater than zero. This works with OpenGL, so we want to
|
||||
// make sure to catch this scenario.
|
||||
assert_invariant(d->gl.samples <= 1);
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and the formats of draw and read buffers are not identical.
|
||||
// However, it's not well-defined in the spec what "format" means. So it's difficult
|
||||
// to have an assert here -- especially when dealing with the default framebuffer
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and (...) the source and destination rectangles are not defined with the
|
||||
// same (X0, Y0) and (X1, Y1) bounds.
|
||||
|
||||
// Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what
|
||||
// happens when blitting from an "implicit" resolve render target (does it work?), so
|
||||
// to ere on the safe side, we don't allow it.
|
||||
if (s->gl.samples > 1) {
|
||||
assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect)));
|
||||
}
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
|
||||
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
|
||||
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
|
||||
GL_COLOR_BUFFER_BIT, glFilterMode);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3496,18 +3701,17 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(state.program);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
|
||||
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
|
||||
// during the draw call when the program is invalid. The shader compile error has already been
|
||||
// dumped to the console at this point, so it's fine to simply return early.
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
|
||||
bool const success = useProgram(p);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
|
||||
// is invalid. The shader compile error has already been dumped to the console at this
|
||||
// point, so it's fine to simply return early.
|
||||
return;
|
||||
}
|
||||
|
||||
useProgram(p);
|
||||
|
||||
GLRenderPrimitive* rp = handle_cast<GLRenderPrimitive *>(rph);
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
|
||||
// Gracefully do nothing if the render primitive has not been set up.
|
||||
VertexBufferHandle vb = rp->gl.vertexBufferWithObjects;
|
||||
@@ -3518,7 +3722,7 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
|
||||
// If necessary, mutate the bindings in the VAO.
|
||||
const GLVertexBuffer* glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
GLVertexBuffer const* const glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
if (UTILS_UNLIKELY(rp->gl.vertexBufferVersion != glvb->bufferObjectsVersion)) {
|
||||
updateVertexArrayObject(rp, glvb);
|
||||
}
|
||||
@@ -3552,17 +3756,16 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(program);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(program);
|
||||
|
||||
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
|
||||
// during the draw call when the program is invalid. The shader compile error has already been
|
||||
// dumped to the console at this point, so it's fine to simply return early.
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
|
||||
bool const success = useProgram(p);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
|
||||
// is invalid. The shader compile error has already been dumped to the console at this
|
||||
// point, so it's fine to simply return early.
|
||||
return;
|
||||
}
|
||||
|
||||
useProgram(p);
|
||||
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
|
||||
@@ -83,11 +83,9 @@ public:
|
||||
}
|
||||
|
||||
struct {
|
||||
GLuint id;
|
||||
union {
|
||||
struct {
|
||||
GLuint id;
|
||||
GLenum binding;
|
||||
};
|
||||
GLenum binding;
|
||||
void* buffer;
|
||||
};
|
||||
} gl;
|
||||
@@ -320,7 +318,7 @@ private:
|
||||
|
||||
void bindTexture(GLuint unit, GLTexture const* t) noexcept;
|
||||
void bindSampler(GLuint unit, GLuint sampler) noexcept;
|
||||
inline void useProgram(OpenGLProgram* p) noexcept;
|
||||
inline bool useProgram(OpenGLProgram* p) noexcept;
|
||||
|
||||
enum class ResolveAction { LOAD, STORE };
|
||||
void resolvePass(ResolveAction action, GLRenderTarget const* rt,
|
||||
@@ -363,7 +361,8 @@ private:
|
||||
void setScissor(Viewport const& scissor) noexcept;
|
||||
|
||||
// ES2 only. Uniform buffer emulation binding points
|
||||
std::array<std::pair<void const*, uint16_t>, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {};
|
||||
GLuint mLastAssignedEmulatedUboId = 0;
|
||||
std::array<std::tuple<GLuint, void const*, uint16_t>, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {};
|
||||
|
||||
// sampler buffer binding points (nullptr if not used)
|
||||
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
|
||||
|
||||
@@ -129,7 +129,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
#endif
|
||||
{
|
||||
// ES2 initialization of (fake) UBOs
|
||||
UniformsRecord* const uniformsRecords = new UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint binding = 0, n = Program::UNIFORM_BINDING_COUNT; binding < n; binding++) {
|
||||
Program::UniformInfo& uniforms = lazyInitializationData.bindingUniformInfo[binding];
|
||||
@@ -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
|
||||
@@ -227,15 +229,16 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLProgram::updateUniforms(uint32_t index, void const* buffer, uint16_t age) noexcept {
|
||||
void OpenGLProgram::updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept {
|
||||
assert_invariant(mUniformsRecords);
|
||||
assert_invariant(buffer);
|
||||
|
||||
// only update the uniforms if the UBO has changed since last time we updated
|
||||
UniformsRecord const& records = mUniformsRecords[index];
|
||||
if (records.age == age) {
|
||||
if (records.id == id && records.age == age) {
|
||||
return;
|
||||
}
|
||||
records.id = id;
|
||||
records.age = age;
|
||||
|
||||
assert_invariant(records.uniforms.size() == records.locations.size());
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
// - the content of any bound sampler buffer has changed
|
||||
// ... since last time we used this program
|
||||
|
||||
// turns out the former might be relatively cheap to check, the later requires
|
||||
// Turns out the former might be relatively cheap to check, the latter requires
|
||||
// a bit less. Compared to what updateSamplers() actually does, which is
|
||||
// pretty little, I'm not sure if we'll get ahead.
|
||||
|
||||
@@ -72,7 +72,7 @@ public:
|
||||
} gl; // 12 bytes
|
||||
|
||||
// For ES2 only
|
||||
void updateUniforms(uint32_t index, void const* buffer, uint16_t age) noexcept;
|
||||
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept;
|
||||
void setRec709ColorSpace(bool rec709) const noexcept;
|
||||
|
||||
private:
|
||||
@@ -98,6 +98,7 @@ private:
|
||||
struct UniformsRecord {
|
||||
Program::UniformInfo uniforms;
|
||||
LocationInfo locations;
|
||||
mutable GLuint id = 0;
|
||||
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
|
||||
};
|
||||
UniformsRecord const* mUniformsRecords = nullptr;
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
@@ -140,20 +139,38 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept
|
||||
|
||||
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
|
||||
: mDriver(driver),
|
||||
mCallbackManager(driver),
|
||||
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
|
||||
mBlobCache(driver.getContext()),
|
||||
mCallbackManager(driver) {
|
||||
}
|
||||
|
||||
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
|
||||
|
||||
bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept {
|
||||
return KHR_parallel_shader_compile || mShaderCompilerThreadCount;
|
||||
assert_invariant(mMode != Mode::UNDEFINED);
|
||||
return mMode != Mode::SYNCHRONOUS;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::init() noexcept {
|
||||
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
|
||||
// friendly.
|
||||
if (!KHR_parallel_shader_compile) {
|
||||
// Here we decide which mode we'll be using. We always prefer our own thread-pool if
|
||||
// that mode is available because, we have no control on how the compilation queues are
|
||||
// handled if done by the driver (so at the very least we'd need to decode this per-driver).
|
||||
// In theory, using Mode::ASYNCHRONOUS (a.k.a. KHR_parallel_shader_compile) could be more
|
||||
// efficient, since it doesn't require shared contexts.
|
||||
// In practice, we already know that with ANGLE, Mode::ASYNCHRONOUS can cause very long
|
||||
// pauses at glDraw() time in the situation where glLinkProgram() has been emitted, but has
|
||||
// other programs ahead of it in ANGLE's queue.
|
||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||
// our thread-pool if possible
|
||||
mMode = Mode::THREAD_POOL;
|
||||
} else if (mDriver.getContext().ext.KHR_parallel_shader_compile) {
|
||||
// if not, async shader compilation and link if the driver supports it
|
||||
mMode = Mode::ASYNCHRONOUS;
|
||||
} else {
|
||||
// fallback to synchronous shader compilation
|
||||
mMode = Mode::SYNCHRONOUS;
|
||||
}
|
||||
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
||||
// also glProgramBinary blocks if other threads are compiling.
|
||||
// - on Mali shader compilation can be multi-threaded, but program linking happens on
|
||||
@@ -163,37 +180,42 @@ void ShaderCompilerService::init() noexcept {
|
||||
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
|
||||
// a shared context, so parallel shader compilation yields no benefit.
|
||||
// - on windows/linux we could use more threads, tbd.
|
||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||
// safest choice that avoids priority inversions.
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
auto const& renderer = mDriver.getContext().state.renderer;
|
||||
if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) {
|
||||
// The PowerVR driver support parallel shader compilation well, so we use 2
|
||||
// threads, we can use lower priority threads here because urgent compilations
|
||||
// will most likely happen on the main gl thread. Using too many thread can
|
||||
// increase memory pressure significantly.
|
||||
poolSize = 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
}
|
||||
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||
// safest choice that avoids priority inversions.
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
JobSystem::setThreadPriority(priority);
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
auto const& renderer = mDriver.getContext().state.renderer;
|
||||
// Some drivers support parallel shader compilation well, so we use N
|
||||
// threads, we can use lower priority threads here because urgent compilations
|
||||
// will most likely happen on the main gl thread. Using too many thread can
|
||||
// increase memory pressure significantly.
|
||||
if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) {
|
||||
// For PowerVR, it's unclear what the cost of extra shared contexts is, so we only
|
||||
// use 2 to be safe.
|
||||
poolSize = 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
} else if (UTILS_UNLIKELY(strstr(renderer, "ANGLE"))) {
|
||||
// Angle shared contexts are not expensive once we have two.
|
||||
poolSize = (std::thread::hardware_concurrency() + 1) / 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
}
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
JobSystem::setThreadPriority(priority);
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +241,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;
|
||||
}
|
||||
@@ -227,7 +249,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
if (mShaderCompilerThreadCount) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// queue a compile job
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
@@ -249,12 +271,10 @@ 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;
|
||||
|
||||
token->gl.program = programData.program;
|
||||
|
||||
// we don't need to check for success here, it'll be done on the
|
||||
// main thread side.
|
||||
token->set(programData);
|
||||
@@ -262,9 +282,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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -278,7 +298,8 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->shaderSourceCode);
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
if (KHR_parallel_shader_compile) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// don't attempt to link this program if all shaders are not done compiling
|
||||
GLint status;
|
||||
if (token->gl.program) {
|
||||
@@ -302,7 +323,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
if (KHR_parallel_shader_compile) {
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// wait until the link finishes...
|
||||
return false;
|
||||
}
|
||||
@@ -317,7 +338,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;
|
||||
@@ -336,14 +357,14 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
return program;
|
||||
}
|
||||
|
||||
/* static*/ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
bool canceled = token->compiler.cancelTickOp(token);
|
||||
bool const canceled = token->compiler.cancelTickOp(token);
|
||||
|
||||
if (token->compiler.mShaderCompilerThreadCount) {
|
||||
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
if (!job) {
|
||||
// The job is being executed right now. We need to wait for it to finish to avoid a
|
||||
@@ -377,7 +398,7 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
|
||||
void ShaderCompilerService::tick() {
|
||||
// we don't need to run executeTickOps() if we're using the thread-pool
|
||||
if (UTILS_UNLIKELY(!mShaderCompilerThreadCount)) {
|
||||
if (UTILS_UNLIKELY(mMode != Mode::THREAD_POOL)) {
|
||||
executeTickOps();
|
||||
}
|
||||
}
|
||||
@@ -402,7 +423,7 @@ void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) n
|
||||
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (!token->gl.program) {
|
||||
if (mShaderCompilerThreadCount) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// we need this program right now, remove it from the queue
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
@@ -418,7 +439,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
// Block until we get the program from the pool. Generally this wouldn't block
|
||||
// because we just compiled the program above, when executing job.
|
||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||
} else if (KHR_parallel_shader_compile) {
|
||||
} else if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// we force the program link -- which might stall, either here or below in
|
||||
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||
token->compiler.cancelTickOp(token);
|
||||
@@ -431,7 +452,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.
|
||||
@@ -526,7 +547,8 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
// add support for ARB_shading_language_packing if needed
|
||||
auto const packingFunctions = process_ARB_shading_language_packing(context);
|
||||
|
||||
// split shader source, so we can insert the specification constants and the packing functions
|
||||
// split shader source, so we can insert the specialization constants and the packing
|
||||
// functions
|
||||
auto const [prolog, body] = splitShaderSource(source);
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include "CompilerThreadPool.h"
|
||||
#include "OpenGLBlobCache.h"
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/Program.h>
|
||||
@@ -94,18 +95,42 @@ public:
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
|
||||
private:
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
}
|
||||
std::function<bool(Job const& job)> fn;
|
||||
CallbackHandler* handler = nullptr;
|
||||
void* user = nullptr;
|
||||
CallbackHandler::Callback callback{};
|
||||
};
|
||||
|
||||
enum class Mode {
|
||||
UNDEFINED, // init() has not beed called yet.
|
||||
SYNCHRONOUS, // synchronous shader compilation
|
||||
THREAD_POOL, // asynchronous shader compilation using a thread-pool (most common)
|
||||
ASYNCHRONOUS // asynchronous shader compilation using KHR_parallel_shader_compile
|
||||
};
|
||||
|
||||
OpenGLDriver& mDriver;
|
||||
OpenGLBlobCache mBlobCache;
|
||||
CallbackManager mCallbackManager;
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
|
||||
const bool KHR_parallel_shader_compile;
|
||||
uint32_t mShaderCompilerThreadCount = 0u;
|
||||
Mode mMode = Mode::UNDEFINED; // valid after init() is called
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
|
||||
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||
|
||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||
|
||||
static void compileShaders(
|
||||
static void compileShaders(
|
||||
OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
@@ -125,27 +150,11 @@ private:
|
||||
|
||||
static bool checkProgramStatus(program_token_t const& token) noexcept;
|
||||
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
}
|
||||
std::function<bool(Job const& job)> fn;
|
||||
CallbackHandler* handler = nullptr;
|
||||
void* user = nullptr;
|
||||
CallbackHandler::Callback callback{};
|
||||
};
|
||||
|
||||
void runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept;
|
||||
void executeTickOps() noexcept;
|
||||
bool cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -28,6 +28,7 @@ static void getProcAddress(T& pfn, const char* name) noexcept {
|
||||
}
|
||||
|
||||
namespace glext {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_OES_EGL_image
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
|
||||
#endif
|
||||
@@ -72,10 +73,11 @@ PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
// use getProcAddress for ES3.1 and above entry points.
|
||||
PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
#endif
|
||||
|
||||
static std::once_flag sGlExtInitialized;
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
void importGLESExtensionsEntryPoints() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::call_once(sGlExtInitialized, +[]() {
|
||||
#ifdef GL_OES_EGL_image
|
||||
getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
|
||||
@@ -119,6 +121,7 @@ void importGLESExtensionsEntryPoints() {
|
||||
getProcAddress(glDispatchCompute, "glDispatchCompute");
|
||||
#endif
|
||||
});
|
||||
#endif // __EMSCRIPTEN__
|
||||
}
|
||||
|
||||
} // namespace glext
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace glext {
|
||||
// it is currently called from PlatformEGL.
|
||||
void importGLESExtensionsEntryPoints();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_OES_EGL_image
|
||||
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
|
||||
#endif
|
||||
@@ -153,6 +154,7 @@ extern PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
|
||||
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
#endif
|
||||
#endif // __EMSCRIPTEN__
|
||||
} // namespace glext
|
||||
|
||||
using namespace glext;
|
||||
@@ -190,8 +192,10 @@ using namespace glext;
|
||||
|
||||
#if defined(GL_EXT_clip_cull_distance)
|
||||
# define GL_CLIP_DISTANCE0 GL_CLIP_DISTANCE0_EXT
|
||||
# define GL_CLIP_DISTANCE1 GL_CLIP_DISTANCE1_EXT
|
||||
#else
|
||||
# define GL_CLIP_DISTANCE0 0x3000
|
||||
# define GL_CLIP_DISTANCE1 0x3001
|
||||
#endif
|
||||
|
||||
#if defined(GL_KHR_debug)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user