Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
031cd302dd |
@@ -206,7 +206,8 @@ jobs:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
run: |
|
||||
build\windows\build-github.bat release
|
||||
move out\filament-windows.tgz out\filament-$Env:TAG-windows.tgz
|
||||
cd ..\..
|
||||
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
|
||||
shell: cmd
|
||||
- uses: actions/github-script@v6
|
||||
env:
|
||||
|
||||
+66
-66
@@ -40,20 +40,20 @@ inside the Filament source tree.
|
||||
|
||||
To trigger an incremental debug build:
|
||||
|
||||
```shell
|
||||
./build.sh debug
|
||||
```
|
||||
$ ./build.sh debug
|
||||
```
|
||||
|
||||
To trigger an incremental release build:
|
||||
|
||||
```shell
|
||||
./build.sh release
|
||||
```
|
||||
$ ./build.sh release
|
||||
```
|
||||
|
||||
To trigger both incremental debug and release builds:
|
||||
|
||||
```shell
|
||||
./build.sh debug release
|
||||
```
|
||||
$ ./build.sh debug release
|
||||
```
|
||||
|
||||
To install the libraries and executables in `out/debug/` and `out/release/`, add the `-i` flag.
|
||||
@@ -76,9 +76,9 @@ The following CMake options are boolean options specific to Filament:
|
||||
|
||||
To turn an option on or off:
|
||||
|
||||
```shell
|
||||
cd <cmake-build-directory>
|
||||
cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF
|
||||
```
|
||||
$ cd <cmake-build-directory>
|
||||
$ cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF
|
||||
```
|
||||
|
||||
Options can also be set with the CMake GUI.
|
||||
@@ -102,38 +102,38 @@ script.
|
||||
If you'd like to run `cmake` directly rather than using the build script, it can be invoked as
|
||||
follows, with some caveats that are explained further down.
|
||||
|
||||
```shell
|
||||
mkdir out/cmake-release
|
||||
cd out/cmake-release
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
```
|
||||
$ mkdir out/cmake-release
|
||||
$ cd out/cmake-release
|
||||
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
```
|
||||
|
||||
Your Linux distribution might default to `gcc` instead of `clang`, if that's the case invoke
|
||||
`cmake` with the following command:
|
||||
|
||||
```shell
|
||||
mkdir out/cmake-release
|
||||
cd out/cmake-release
|
||||
```
|
||||
$ mkdir out/cmake-release
|
||||
$ cd out/cmake-release
|
||||
# Or use a specific version of clang, for instance /usr/bin/clang-14
|
||||
CC=/usr/bin/clang CXX=/usr/bin/clang++ CXXFLAGS=-stdlib=libc++ \
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
$ CC=/usr/bin/clang CXX=/usr/bin/clang++ CXXFLAGS=-stdlib=libc++ \
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
```
|
||||
|
||||
You can also export the `CC` and `CXX` environment variables to always point to `clang`. Another
|
||||
solution is to use `update-alternatives` to both change the default compiler, and point to a
|
||||
specific version of clang:
|
||||
|
||||
```shell
|
||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-14 100
|
||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-14 100
|
||||
update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
|
||||
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||
```
|
||||
$ update-alternatives --install /usr/bin/clang clang /usr/bin/clang-14 100
|
||||
$ update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-14 100
|
||||
$ update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
|
||||
$ update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||
```
|
||||
|
||||
Finally, invoke `ninja`:
|
||||
|
||||
```shell
|
||||
ninja
|
||||
```
|
||||
$ ninja
|
||||
```
|
||||
|
||||
This will build Filament, its tests and samples, and various host tools.
|
||||
@@ -143,8 +143,8 @@ This will build Filament, its tests and samples, and various host tools.
|
||||
To compile Filament you must have the most recent version of Xcode installed and you need to
|
||||
make sure the command line tools are setup by running:
|
||||
|
||||
```shell
|
||||
xcode-select --install
|
||||
```
|
||||
$ xcode-select --install
|
||||
```
|
||||
|
||||
If you wish to run the Vulkan backend instead of the default Metal backend, you must install
|
||||
@@ -152,11 +152,11 @@ the LunarG SDK, enable "System Global Components", and reboot your machine.
|
||||
|
||||
Then run `cmake` and `ninja` to trigger a build:
|
||||
|
||||
```shell
|
||||
mkdir out/cmake-release
|
||||
cd out/cmake-release
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
ninja
|
||||
```
|
||||
$ mkdir out/cmake-release
|
||||
$ cd out/cmake-release
|
||||
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
$ ninja
|
||||
```
|
||||
|
||||
### iOS
|
||||
@@ -164,8 +164,8 @@ ninja
|
||||
The easiest way to build Filament for iOS is to use `build.sh` and the
|
||||
`-p ios` flag. For instance to build the debug target:
|
||||
|
||||
```shell
|
||||
./build.sh -p ios debug
|
||||
```
|
||||
$ ./build.sh -p ios debug
|
||||
```
|
||||
|
||||
See [ios/samples/README.md](./ios/samples/README.md) for more information.
|
||||
@@ -191,10 +191,10 @@ using `fsutil.exe file queryCaseSensitiveInfo`.
|
||||
Next, open `x64 Native Tools Command Prompt for VS 2019`, create a working directory, and run
|
||||
CMake in it:
|
||||
|
||||
```bat
|
||||
mkdir out
|
||||
cd out
|
||||
cmake ..
|
||||
```
|
||||
> mkdir out
|
||||
> cd out
|
||||
> cmake ..
|
||||
```
|
||||
|
||||
Open the generated solution file `TNT.sln` in Visual Studio.
|
||||
@@ -204,15 +204,15 @@ target in the _Solution Explorer_ and choose _Build_ to build a specific target.
|
||||
|
||||
For example, build the `material_sandbox` sample and run it from the `out` directory with:
|
||||
|
||||
```bat
|
||||
samples\Debug\material_sandbox.exe ..\assets\models\monkey\monkey.obj
|
||||
```
|
||||
> samples\Debug\material_sandbox.exe ..\assets\models\monkey\monkey.obj
|
||||
```
|
||||
|
||||
You can also use CMake to invoke the build without opening Visual Studio. For example, from the
|
||||
`out` folder run the following command.
|
||||
|
||||
```bat
|
||||
cmake --build . --target gltf_viewer --config Release
|
||||
```
|
||||
> cmake --build . --target gltf_viewer --config Release
|
||||
```
|
||||
|
||||
### Android
|
||||
@@ -237,8 +237,8 @@ To build Android on Windows machines, see [android/Windows.md](android/Windows.m
|
||||
The easiest way to build Filament for Android is to use `build.sh` and the
|
||||
`-p android` flag. For instance to build the release target:
|
||||
|
||||
```shell
|
||||
./build.sh -p android release
|
||||
```
|
||||
$ ./build.sh -p android release
|
||||
```
|
||||
|
||||
Run `build.sh -h` for more information.
|
||||
@@ -248,23 +248,23 @@ Run `build.sh -h` for more information.
|
||||
Invoke CMake in a build directory of your choice, inside of filament's directory. The commands
|
||||
below show how to build Filament for ARM 64-bit (`aarch64`).
|
||||
|
||||
```shell
|
||||
mkdir out/android-build-release-aarch64
|
||||
cd out/android-build-release-aarch64
|
||||
cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=../../build/toolchain-aarch64-linux-android.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../android-release/filament ../..
|
||||
```
|
||||
$ mkdir out/android-build-release-aarch64
|
||||
$ cd out/android-build-release-aarch64
|
||||
$ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=../../build/toolchain-aarch64-linux-android.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../android-release/filament ../..
|
||||
```
|
||||
|
||||
And then invoke `ninja`:
|
||||
|
||||
```shell
|
||||
ninja install
|
||||
```
|
||||
$ ninja install
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
ninja install/strip
|
||||
```
|
||||
$ ninja install/strip
|
||||
```
|
||||
|
||||
This will generate Filament's Android binaries in `out/android-release`. This location is important
|
||||
@@ -296,8 +296,8 @@ AAR.
|
||||
Alternatively you can build the AAR from the command line by executing the following in the
|
||||
`android/` directory:
|
||||
|
||||
```shell
|
||||
./gradlew -Pcom.google.android.filament.dist-dir=../../out/android-release/filament assembleRelease
|
||||
```
|
||||
$ ./gradlew -Pcom.google.android.filament.dist-dir=../../out/android-release/filament assembleRelease
|
||||
```
|
||||
|
||||
The `-Pcom.google.android.filament.dist-dir` can be used to specify a different installation
|
||||
@@ -311,7 +311,7 @@ sure to add the newly created module as a dependency to your application.
|
||||
If you do not wish to include all supported ABIs, make sure to create the appropriate flavors in
|
||||
your Gradle build file. For example:
|
||||
|
||||
```gradle
|
||||
```
|
||||
flavorDimensions 'cpuArch'
|
||||
productFlavors {
|
||||
arm8 {
|
||||
@@ -353,7 +353,7 @@ started, follow the instructions for building Filament on your platform ([macOS]
|
||||
Next, you need to install the Emscripten SDK. The following instructions show how to install the
|
||||
same version that our continuous builds use.
|
||||
|
||||
```shell
|
||||
```
|
||||
cd <your chosen parent folder for the emscripten SDK>
|
||||
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.15.zip > emsdk.zip
|
||||
unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk
|
||||
@@ -364,7 +364,7 @@ source ./emsdk_env.sh
|
||||
|
||||
After this you can invoke the [easy build](#easy-build) script as follows:
|
||||
|
||||
```shell
|
||||
```
|
||||
export EMSDK=<your chosen home for the emscripten SDK>
|
||||
./build.sh -p webgl release
|
||||
```
|
||||
@@ -374,7 +374,7 @@ creates a `samples` folder that can be used as the root of a simple static web s
|
||||
cannot open the HTML directly from the filesystem due to CORS. We recommend using the emrun tool
|
||||
to create a quick localhost server:
|
||||
|
||||
```shell
|
||||
```
|
||||
emrun out/cmake-webgl-release/web/samples --no_browser --port 8000
|
||||
```
|
||||
|
||||
@@ -395,7 +395,7 @@ Some of the samples accept FBX/OBJ meshes while others rely on the `filamesh` fi
|
||||
generate a `filamesh ` file from an FBX/OBJ asset, run the `filamesh` tool
|
||||
(`./tools/filamesh/filamesh` in your build directory):
|
||||
|
||||
```shell
|
||||
```
|
||||
filamesh ./assets/models/monkey/monkey.obj monkey.filamesh
|
||||
```
|
||||
|
||||
@@ -405,7 +405,7 @@ files for the IBL (which are PNGs containing `R11F_G11F_B10F` data) or a path to
|
||||
containing two `.ktx` files (one for the IBL itself, one for the skybox). To generate an IBL
|
||||
simply use this command:
|
||||
|
||||
```shell
|
||||
```
|
||||
cmgen -f ktx -x ./ibls/ my_ibl.exr
|
||||
```
|
||||
|
||||
@@ -427,9 +427,9 @@ value is the desired roughness between 0 and 1.
|
||||
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
|
||||
following commands:
|
||||
|
||||
```shell
|
||||
cd filament/filament
|
||||
doxygen docs/doxygen/filament.doxygen
|
||||
```
|
||||
$ cd filament/filament
|
||||
$ doxygen docs/doxygen/filament.doxygen
|
||||
```
|
||||
|
||||
Finally simply open `docs/html/index.html` in your web browser.
|
||||
@@ -439,7 +439,7 @@ Finally simply open `docs/html/index.html` in your web browser.
|
||||
To try out Filament's Vulkan support with SwiftShader, first build SwiftShader and set the
|
||||
`SWIFTSHADER_LD_LIBRARY_PATH` variable to the folder that contains `libvk_swiftshader.dylib`:
|
||||
|
||||
```shell
|
||||
```
|
||||
git clone https://github.com/google/swiftshader.git
|
||||
cd swiftshader/build
|
||||
cmake .. && make -j
|
||||
@@ -454,7 +454,7 @@ Continuous testing turnaround can be quite slow if you need to build SwiftShader
|
||||
provide an Ubuntu-based Docker image that has it already built. The Docker image also includes
|
||||
everything necessary for building Filament. You can fetch and run the image as follows:
|
||||
|
||||
```shell
|
||||
```
|
||||
docker pull ghcr.io/filament-assets/swiftshader
|
||||
docker run -it ghcr.io/filament-assets/swiftshader
|
||||
```
|
||||
|
||||
@@ -3,14 +3,6 @@
|
||||
# ==================================================================================================
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
# ==================================================================================================
|
||||
# Toolchain configuration
|
||||
# ==================================================================================================
|
||||
if (APPLE AND NOT IOS)
|
||||
# This must be set before project() is called
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "")
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Project declaration
|
||||
# ==================================================================================================
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ again.
|
||||
|
||||
## Code Style
|
||||
|
||||
See [CODE_STYLE.md](/CODE_STYLE.md)
|
||||
See [CodeStyle.md](/CODE_STYLE.md)
|
||||
|
||||
## Code reviews
|
||||
|
||||
|
||||
@@ -7,3 +7,9 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Fix possible NPE when updating fog options from Java/Kotlin
|
||||
- The `emissive` property was not applied properly to `MASKED` materials, and could cause
|
||||
dark fringes to appear (recompile materials)
|
||||
- Allow glTF materials with transmission/volume extensions to choose their alpha mode
|
||||
instead of forcing `MASKED`
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.43.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.42.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -50,8 +50,8 @@ 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.42.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,30 +7,8 @@ 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.43.1
|
||||
|
||||
## v1.43.0
|
||||
|
||||
- gltfio: Fix possible change of scale sign when decomposing transform matrix for animation
|
||||
- engine: Fixes "stable" shadows (see b/299310624)
|
||||
|
||||
## v1.42.2
|
||||
|
||||
- Fix possible NPE when updating fog options from Java/Kotlin
|
||||
- The `emissive` property was not applied properly to `MASKED` materials, and could cause
|
||||
dark fringes to appear (recompile materials)
|
||||
- Allow glTF materials with transmission/volume extensions to choose their alpha mode
|
||||
instead of forcing `MASKED`
|
||||
- Fix a crash in gltfio when not using ubershaders
|
||||
- Use flatmat for mat parameter in jsbinding
|
||||
- Fix TextureFlags for sheenRoughnessMap when textures of sheenRoughnessMap and sheenColorMap is same
|
||||
- Directional shadows can now be transformed (b/297095805)
|
||||
|
||||
## v1.42.1
|
||||
|
||||
- Fix potential `EXC_BAD_ACCESS` with Metal backend: b/297059776
|
||||
- `setFrameCompletedCallback` now takes a `backend::CallbackHandler`.
|
||||
|
||||
## v1.42.0
|
||||
|
||||
- engine: add preliminary support for instanced stereoscopic rendering [⚠️ **Recompile materials**]
|
||||
|
||||
@@ -79,8 +79,7 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env,
|
||||
jfloat shadowFarHint, jboolean stable, jboolean lispsm,
|
||||
jfloat polygonOffsetConstant, jfloat polygonOffsetSlope,
|
||||
jboolean screenSpaceContactShadows, jint stepCount,
|
||||
jfloat maxShadowDistance, jboolean elvsm, jfloat blurWidth, jfloat shadowBulbRadius,
|
||||
jfloatArray transform) {
|
||||
jfloat maxShadowDistance, jboolean elvsm, jfloat blurWidth, jfloat shadowBulbRadius) {
|
||||
LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder;
|
||||
LightManager::ShadowOptions shadowOptions {
|
||||
.mapSize = (uint32_t)mapSize,
|
||||
@@ -103,18 +102,12 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env,
|
||||
},
|
||||
.shadowBulbRadius = shadowBulbRadius
|
||||
};
|
||||
|
||||
jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL);
|
||||
const jsize splitCount = std::min((jsize) 3, env->GetArrayLength(splitPositions));
|
||||
std::copy_n(nativeSplits, splitCount, shadowOptions.cascadeSplitPositions);
|
||||
for (jsize i = 0; i < splitCount; i++) {
|
||||
shadowOptions.cascadeSplitPositions[i] = nativeSplits[i];
|
||||
}
|
||||
env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0);
|
||||
|
||||
jfloat* nativeTransform = env->GetFloatArrayElements(transform, NULL);
|
||||
std::copy_n(nativeTransform,
|
||||
std::min(4, env->GetArrayLength(transform)),
|
||||
shadowOptions.transform.xyzw.v);
|
||||
env->ReleaseFloatArrayElements(transform, nativeTransform, 0);
|
||||
|
||||
builder->shadowOptions(shadowOptions);
|
||||
}
|
||||
|
||||
|
||||
+2
-14
@@ -258,7 +258,6 @@ public class LightManager {
|
||||
* shadows that are too far and wouldn't contribute to the scene much, improving
|
||||
* performance and quality. This value is always positive.
|
||||
* Use 0.0f to use the camera far distance.
|
||||
* This only affect directional lights.
|
||||
*/
|
||||
public float shadowFar = 0.0f;
|
||||
|
||||
@@ -369,17 +368,6 @@ public class LightManager {
|
||||
* enabled. (2cm by default).
|
||||
*/
|
||||
public float shadowBulbRadius = 0.02f;
|
||||
|
||||
/**
|
||||
* Transforms the shadow direction. Must be a unit quaternion.
|
||||
* The default is identity.
|
||||
* Ignored if the light type isn't directional. For artistic use. Use with caution.
|
||||
* The quaternion is stored as the imaginary part in the first 3 elements and the real
|
||||
* part in the last element of the transform array.
|
||||
*/
|
||||
@NonNull
|
||||
@Size(min = 4, max = 4)
|
||||
public float[] transform = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
}
|
||||
|
||||
public static class ShadowCascades {
|
||||
@@ -518,7 +506,7 @@ public class LightManager {
|
||||
options.polygonOffsetConstant, options.polygonOffsetSlope,
|
||||
options.screenSpaceContactShadows,
|
||||
options.stepCount, options.maxShadowDistance,
|
||||
options.elvsm, options.blurWidth, options.shadowBulbRadius, options.transform);
|
||||
options.elvsm, options.blurWidth, options.shadowBulbRadius);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1181,7 +1169,7 @@ public class LightManager {
|
||||
boolean stable, boolean lispsm,
|
||||
float polygonOffsetConstant, float polygonOffsetSlope,
|
||||
boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance,
|
||||
boolean elvsm, float blurWidth, float shadowBulbRadius, float[] transform);
|
||||
boolean elvsm, float blurWidth, float shadowBulbRadius);
|
||||
private static native void nBuilderCastLight(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderPosition(long nativeBuilder, float x, float y, float z);
|
||||
private static native void nBuilderDirection(long nativeBuilder, float x, float y, float z);
|
||||
|
||||
@@ -1418,17 +1418,6 @@ public class View {
|
||||
* limit highlights to this value before bloom [10, +inf]
|
||||
*/
|
||||
public float highlight = 1000.0f;
|
||||
/**
|
||||
* Bloom quality level.
|
||||
* LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.
|
||||
* MEDIUM: Good balance between quality and performance.
|
||||
* HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||
* This mode can be significantly slower on mobile, especially at high resolution.
|
||||
* This mode greatly improves the anamorphic bloom.
|
||||
*/
|
||||
@NonNull
|
||||
public QualityLevel quality = QualityLevel.LOW;
|
||||
/**
|
||||
* enable screen-space lens flare
|
||||
*/
|
||||
|
||||
@@ -52,7 +52,6 @@ set(GLTFIO_SRCS
|
||||
${GLTFIO_DIR}/include/gltfio/FilamentInstance.h
|
||||
${GLTFIO_DIR}/include/gltfio/MaterialProvider.h
|
||||
${GLTFIO_DIR}/include/gltfio/NodeManager.h
|
||||
${GLTFIO_DIR}/include/gltfio/TrsTransformManager.h
|
||||
${GLTFIO_DIR}/include/gltfio/ResourceLoader.h
|
||||
${GLTFIO_DIR}/include/gltfio/TextureProvider.h
|
||||
${GLTFIO_DIR}/include/gltfio/math.h
|
||||
@@ -70,12 +69,10 @@ set(GLTFIO_SRCS
|
||||
${GLTFIO_DIR}/src/FilamentAsset.cpp
|
||||
${GLTFIO_DIR}/src/FilamentInstance.cpp
|
||||
${GLTFIO_DIR}/src/FNodeManager.h
|
||||
${GLTFIO_DIR}/src/FTrsTransformManager.h
|
||||
${GLTFIO_DIR}/src/GltfEnums.h
|
||||
${GLTFIO_DIR}/src/Ktx2Provider.cpp
|
||||
${GLTFIO_DIR}/src/MaterialProvider.cpp
|
||||
${GLTFIO_DIR}/src/NodeManager.cpp
|
||||
${GLTFIO_DIR}/src/TrsTransformManager.cpp
|
||||
${GLTFIO_DIR}/src/ResourceLoader.cpp
|
||||
${GLTFIO_DIR}/src/StbProvider.cpp
|
||||
${GLTFIO_DIR}/src/TangentsJob.cpp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.43.1
|
||||
VERSION_NAME=1.42.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -87,9 +87,9 @@ compile Filament's native library and Filament's AAR for this project. The easie
|
||||
is to install all the required dependencies and to run the following commands at the root of the
|
||||
source tree:
|
||||
|
||||
```shell
|
||||
./build.sh -p desktop -i release
|
||||
./build.sh -p android release
|
||||
```
|
||||
$ ./build.sh -p desktop -i release
|
||||
$ ./build.sh -p android release
|
||||
```
|
||||
|
||||
This will build all the native components and the AAR required by this sample application.
|
||||
@@ -100,8 +100,8 @@ distribution/install directory for desktop (produced by make/ninja install). Thi
|
||||
contain `bin/matc` and `bin/cmgen`.
|
||||
|
||||
Example:
|
||||
```shell
|
||||
./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
|
||||
```
|
||||
$ ./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
|
||||
```
|
||||
|
||||
## Important: SDK location
|
||||
@@ -110,24 +110,14 @@ Either ensure your `ANDROID_HOME` environment variable is set or make sure the r
|
||||
contains a `local.properties` file with the `sdk.dir` property pointing to your installation of
|
||||
the Android SDK.
|
||||
|
||||
## Compiling
|
||||
|
||||
### Android Studio
|
||||
## Android Studio
|
||||
|
||||
You must use the latest stable release of Android Studio. To open the project, point Studio to the
|
||||
`android` folder. After opening the project and syncing to gradle, select the sample of your choice
|
||||
using the drop-down widget in the toolbar.
|
||||
|
||||
## Compiling
|
||||
|
||||
To compile and run each sample make sure you have selected the appropriate build variant
|
||||
(arm7, arm8, x86 or x86_64). If you are not sure you can simply select the "universal"
|
||||
variant which includes all the other ones.
|
||||
|
||||
### Command Line
|
||||
|
||||
From the `android` directory in the project root:
|
||||
|
||||
```shell
|
||||
./gradlew :samples:sample-hello-triangle:installDebug
|
||||
```
|
||||
|
||||
Replace `sample-hello-triangle` with your preferred project.
|
||||
|
||||
@@ -210,10 +210,13 @@ function build_desktop_target {
|
||||
echo "Building ${lc_target} in out/cmake-${lc_target}..."
|
||||
mkdir -p "out/cmake-${lc_target}"
|
||||
|
||||
pushd "out/cmake-${lc_target}" > /dev/null
|
||||
cd "out/cmake-${lc_target}"
|
||||
|
||||
# On macOS, set the deployment target to 10.15.
|
||||
local lc_name=$(echo "${UNAME}" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ "${lc_name}" == "darwin" ]]; then
|
||||
local deployment_target="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15"
|
||||
|
||||
if [[ "${BUILD_UNIVERSAL_LIBRARIES}" == "true" ]]; then
|
||||
local architectures="-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64"
|
||||
fi
|
||||
@@ -230,6 +233,7 @@ function build_desktop_target {
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
${ASAN_UBSAN_OPTION} \
|
||||
${deployment_target} \
|
||||
${architectures} \
|
||||
../..
|
||||
fi
|
||||
@@ -243,13 +247,12 @@ function build_desktop_target {
|
||||
if [[ -d "../${lc_target}/filament" ]]; then
|
||||
if [[ "${ISSUE_ARCHIVES}" == "true" ]]; then
|
||||
echo "Generating out/filament-${lc_target}-${LC_UNAME}.tgz..."
|
||||
pushd "../${lc_target}" > /dev/null
|
||||
cd "../${lc_target}"
|
||||
tar -czvf "../filament-${lc_target}-${LC_UNAME}.tgz" filament
|
||||
popd > /dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
popd > /dev/null
|
||||
cd ../..
|
||||
}
|
||||
|
||||
function build_desktop {
|
||||
@@ -267,7 +270,7 @@ function build_webgl_with_target {
|
||||
|
||||
echo "Building WebGL ${lc_target}..."
|
||||
mkdir -p "out/cmake-webgl-${lc_target}"
|
||||
pushd "out/cmake-webgl-${lc_target}" > /dev/null
|
||||
cd "out/cmake-webgl-${lc_target}"
|
||||
|
||||
if [[ ! "${BUILD_TARGETS}" ]]; then
|
||||
BUILD_TARGETS=${BUILD_CUSTOM_TARGETS}
|
||||
@@ -304,17 +307,17 @@ function build_webgl_with_target {
|
||||
|
||||
if [[ "${ISSUE_ARCHIVES}" == "true" ]]; then
|
||||
echo "Generating out/filament-${lc_target}-web.tgz..."
|
||||
pushd web/filament-js > /dev/null
|
||||
cd web/filament-js
|
||||
tar -cvf "../../../filament-${lc_target}-web.tar" filament.js
|
||||
tar -rvf "../../../filament-${lc_target}-web.tar" filament.wasm
|
||||
tar -rvf "../../../filament-${lc_target}-web.tar" filament.d.ts
|
||||
popd > /dev/null
|
||||
cd -
|
||||
gzip -c "../filament-${lc_target}-web.tar" > "../filament-${lc_target}-web.tgz"
|
||||
rm "../filament-${lc_target}-web.tar"
|
||||
fi
|
||||
fi
|
||||
|
||||
popd > /dev/null
|
||||
cd ../..
|
||||
}
|
||||
|
||||
function build_webgl {
|
||||
@@ -345,7 +348,7 @@ function build_android_target {
|
||||
echo "Building Android ${lc_target} (${arch})..."
|
||||
mkdir -p "out/cmake-android-${lc_target}-${arch}"
|
||||
|
||||
pushd "out/cmake-android-${lc_target}-${arch}" > /dev/null
|
||||
cd "out/cmake-android-${lc_target}-${arch}"
|
||||
|
||||
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
|
||||
cmake \
|
||||
@@ -364,7 +367,7 @@ function build_android_target {
|
||||
# We must always install Android libraries to build the AAR
|
||||
${BUILD_COMMAND} install
|
||||
|
||||
popd > /dev/null
|
||||
cd ../..
|
||||
}
|
||||
|
||||
function build_android_arch {
|
||||
@@ -385,9 +388,9 @@ function archive_android {
|
||||
if [[ -d "out/android-${lc_target}/filament" ]]; then
|
||||
if [[ "${ISSUE_ARCHIVES}" == "true" ]]; then
|
||||
echo "Generating out/filament-android-${lc_target}-${LC_UNAME}.tgz..."
|
||||
pushd "out/android-${lc_target}" > /dev/null
|
||||
cd "out/android-${lc_target}"
|
||||
tar -czvf "../filament-android-${lc_target}-${LC_UNAME}.tgz" filament
|
||||
popd > /dev/null
|
||||
cd ../..
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@@ -465,7 +468,7 @@ function build_android {
|
||||
archive_android "Release"
|
||||
fi
|
||||
|
||||
pushd android > /dev/null
|
||||
cd android
|
||||
|
||||
if [[ "${ISSUE_DEBUG_BUILD}" == "true" ]]; then
|
||||
./gradlew \
|
||||
@@ -566,7 +569,7 @@ function build_android {
|
||||
fi
|
||||
fi
|
||||
|
||||
popd > /dev/null
|
||||
cd ..
|
||||
}
|
||||
|
||||
function build_ios_target {
|
||||
@@ -577,7 +580,7 @@ function build_ios_target {
|
||||
echo "Building iOS ${lc_target} (${arch}) for ${platform}..."
|
||||
mkdir -p "out/cmake-ios-${lc_target}-${arch}"
|
||||
|
||||
pushd "out/cmake-ios-${lc_target}-${arch}" > /dev/null
|
||||
cd "out/cmake-ios-${lc_target}-${arch}"
|
||||
|
||||
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
|
||||
cmake \
|
||||
@@ -601,7 +604,7 @@ function build_ios_target {
|
||||
${BUILD_COMMAND} ${INSTALL_COMMAND}
|
||||
fi
|
||||
|
||||
popd > /dev/null
|
||||
cd ../..
|
||||
}
|
||||
|
||||
function archive_ios {
|
||||
@@ -610,9 +613,9 @@ function archive_ios {
|
||||
if [[ -d "out/ios-${lc_target}/filament" ]]; then
|
||||
if [[ "${ISSUE_ARCHIVES}" == "true" ]]; then
|
||||
echo "Generating out/filament-${lc_target}-ios.tgz..."
|
||||
pushd "out/ios-${lc_target}" > /dev/null
|
||||
cd "out/ios-${lc_target}"
|
||||
tar -czvf "../filament-${lc_target}-ios.tgz" filament
|
||||
popd > /dev/null
|
||||
cd ../..
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@@ -671,14 +674,14 @@ function build_web_docs {
|
||||
|
||||
mkdir -p out/web-docs
|
||||
cp -f docs/web-docs-package.json out/web-docs/package.json
|
||||
pushd out/web-docs > /dev/null
|
||||
cd out/web-docs
|
||||
|
||||
npm install > /dev/null
|
||||
|
||||
# Generate documents
|
||||
npx markdeep-rasterizer ../../docs/Filament.md.html ../../docs/Materials.md.html ../../docs/
|
||||
|
||||
popd > /dev/null
|
||||
cd ../..
|
||||
}
|
||||
|
||||
function validate_build_command {
|
||||
@@ -751,16 +754,6 @@ function run_tests {
|
||||
fi
|
||||
}
|
||||
|
||||
function check_debug_release_build {
|
||||
if [[ "${ISSUE_DEBUG_BUILD}" == "true" || "${ISSUE_RELEASE_BUILD}" == "true" ]]; then
|
||||
"$@";
|
||||
else
|
||||
echo "You must declare a debug or release target for $@ builds."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Beginning of the script
|
||||
|
||||
pushd "$(dirname "$0")" > /dev/null
|
||||
@@ -769,7 +762,7 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
case ${opt} in
|
||||
h)
|
||||
print_help
|
||||
exit 0
|
||||
exit 1
|
||||
;;
|
||||
a)
|
||||
ISSUE_ARCHIVES=true
|
||||
@@ -805,7 +798,7 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
platforms=$(echo "${OPTARG}" | tr ',' '\n')
|
||||
for platform in ${platforms}
|
||||
do
|
||||
case $(echo "${platform}" | tr '[:upper:]' '[:lower:]') in
|
||||
case ${platform} in
|
||||
desktop)
|
||||
ISSUE_DESKTOP_BUILD=true
|
||||
;;
|
||||
@@ -824,12 +817,6 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
ISSUE_DESKTOP_BUILD=true
|
||||
ISSUE_WEBGL_BUILD=false
|
||||
;;
|
||||
*)
|
||||
echo "Unknown platform ${platform}"
|
||||
echo "Platform must be one of [desktop|android|ios|webgl|all]"
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
;;
|
||||
@@ -842,7 +829,7 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
abis=$(echo "${OPTARG}" | tr ',' '\n')
|
||||
for abi in ${abis}
|
||||
do
|
||||
case $(echo "${abi}" | tr '[:upper:]' '[:lower:]') in
|
||||
case ${abi} in
|
||||
armeabi-v7a)
|
||||
ABI_ARMEABI_V7A=true
|
||||
;;
|
||||
@@ -861,12 +848,6 @@ while getopts ":hacCfgijmp:q:uvslwtedk:b" opt; do
|
||||
ABI_X86=true
|
||||
ABI_X86_64=true
|
||||
;;
|
||||
*)
|
||||
echo "Unknown abi ${abi}"
|
||||
echo "ABI must be one of [armeabi-v7a|arm64-v8a|x86|x86_64|all]"
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
;;
|
||||
@@ -930,9 +911,9 @@ fi
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
for arg; do
|
||||
if [[ $(echo "${arg}" | tr '[:upper:]' '[:lower:]') == "release" ]]; then
|
||||
if [[ "${arg}" == "release" ]]; then
|
||||
ISSUE_RELEASE_BUILD=true
|
||||
elif [[ $(echo "${arg}" | tr '[:upper:]' '[:lower:]') == "debug" ]]; then
|
||||
elif [[ "${arg}" == "debug" ]]; then
|
||||
ISSUE_DEBUG_BUILD=true
|
||||
else
|
||||
BUILD_CUSTOM_TARGETS="${BUILD_CUSTOM_TARGETS} ${arg}"
|
||||
@@ -950,19 +931,19 @@ if [[ "${ISSUE_CLEAN_AGGRESSIVE}" == "true" ]]; then
|
||||
fi
|
||||
|
||||
if [[ "${ISSUE_DESKTOP_BUILD}" == "true" ]]; then
|
||||
check_debug_release_build build_desktop
|
||||
build_desktop
|
||||
fi
|
||||
|
||||
if [[ "${ISSUE_ANDROID_BUILD}" == "true" ]]; then
|
||||
check_debug_release_build build_android
|
||||
build_android
|
||||
fi
|
||||
|
||||
if [[ "${ISSUE_IOS_BUILD}" == "true" ]]; then
|
||||
check_debug_release_build build_ios
|
||||
build_ios
|
||||
fi
|
||||
|
||||
if [[ "${ISSUE_WEBGL_BUILD}" == "true" ]]; then
|
||||
check_debug_release_build build_webgl
|
||||
build_webgl
|
||||
fi
|
||||
|
||||
if [[ "${ISSUE_WEB_DOCS}" == "true" ]]; then
|
||||
|
||||
@@ -230,8 +230,6 @@ set(MATERIAL_SRCS
|
||||
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/ssao/bilateralBlur.mat
|
||||
src/materials/ssao/bilateralBlurBentNormals.mat
|
||||
|
||||
+8
-8
@@ -61,7 +61,7 @@ with the platform name, for example, `filament-20181009-linux.tgz`.
|
||||
|
||||
Create a file, `main.cpp`, in the same directory with the following contents:
|
||||
|
||||
```c++
|
||||
```
|
||||
#include <filament/FilamentAPI.h>
|
||||
#include <filament/Engine.h>
|
||||
|
||||
@@ -91,7 +91,7 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory.
|
||||
|
||||
### Linux
|
||||
|
||||
```make
|
||||
```
|
||||
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl
|
||||
CC=clang++
|
||||
|
||||
@@ -109,7 +109,7 @@ clean:
|
||||
|
||||
### macOS
|
||||
|
||||
```make
|
||||
```
|
||||
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl
|
||||
FRAMEWORKS=-framework Cocoa -framework Metal -framework CoreVideo
|
||||
CC=clang++
|
||||
@@ -137,7 +137,7 @@ be sure to also include `matdbg.lib` in `FILAMENT_LIBS`.
|
||||
When building Filament from source, the `USE_STATIC_CRT` CMake option can be
|
||||
used to change the run-time library version.
|
||||
|
||||
```make
|
||||
```
|
||||
FILAMENT_LIBS=filament.lib backend.lib bluegl.lib bluevk.lib filabridge.lib filaflat.lib \
|
||||
utils.lib geometry.lib smol-v.lib ibl.lib vkshaders.lib
|
||||
CC=cl.exe
|
||||
@@ -171,12 +171,12 @@ and invoke `nmake` instead of `make`.
|
||||
|
||||
### Generating C++ documentation
|
||||
|
||||
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
|
||||
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
|
||||
following commands:
|
||||
|
||||
```shell
|
||||
cd filament/filament
|
||||
doxygen docs/doxygen/filament.doxygen
|
||||
```
|
||||
$ cd filament/filament
|
||||
$ doxygen docs/doxygen/filament.doxygen
|
||||
```
|
||||
|
||||
Finally simply open `docs/html/index.html` in your web browser.
|
||||
|
||||
@@ -412,7 +412,6 @@ if (APPLE)
|
||||
test/test_RenderExternalImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(backend_test PRIVATE
|
||||
|
||||
@@ -813,22 +813,22 @@ struct SamplerParams { // NOLINT
|
||||
struct Hasher {
|
||||
size_t operator()(SamplerParams p) const noexcept {
|
||||
// we don't use std::hash<> here, so we don't have to include <functional>
|
||||
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
|
||||
return *reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&p));
|
||||
}
|
||||
};
|
||||
|
||||
struct EqualTo {
|
||||
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
auto* pLhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct LessThan {
|
||||
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
auto* pLhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
@@ -838,7 +838,6 @@ private:
|
||||
return SamplerParams::LessThan{}(lhs, rhs);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(SamplerParams) == 4);
|
||||
|
||||
// The limitation to 64-bits max comes from how we store a SamplerParams in our JNI code
|
||||
// see android/.../TextureSampler.cpp
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
Program& operator=(const Program& rhs) = delete;
|
||||
|
||||
Program(Program&& rhs) noexcept;
|
||||
Program& operator=(Program&& rhs) noexcept = delete;
|
||||
Program& operator=(Program&& rhs) noexcept;
|
||||
|
||||
~Program() noexcept;
|
||||
|
||||
@@ -104,9 +104,8 @@ public:
|
||||
Sampler const* samplers, size_t count) noexcept;
|
||||
|
||||
struct SpecializationConstant {
|
||||
using Type = std::variant<int32_t, float, bool>;
|
||||
uint32_t id; // id set in glsl
|
||||
Type value; // value and type
|
||||
std::variant<int32_t, float, bool> value; // value and type
|
||||
};
|
||||
|
||||
Program& specializationConstants(
|
||||
|
||||
@@ -73,14 +73,32 @@ public:
|
||||
// a cost here (writing and reading the stack at each iteration), in the end it's
|
||||
// probably better to pay the cost at just one location.
|
||||
intptr_t next;
|
||||
driver.mCurrentExecutingCommand = this;
|
||||
mExecute(driver, this, &next);
|
||||
return reinterpret_cast<CommandBase*>(reinterpret_cast<intptr_t>(this) + next);
|
||||
}
|
||||
|
||||
inline void captureCallstack() noexcept {
|
||||
auto c = utils::CallStack::unwind(4);
|
||||
size_t i = 0;
|
||||
for (; i < c.getFrameCount() && i < 16; i++) {
|
||||
mCallstack[i] = c[i];
|
||||
}
|
||||
for (; i < 16; i++) {
|
||||
mCallstack[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void printCallstack() noexcept {
|
||||
auto c = utils::CallStack(mCallstack);
|
||||
utils::slog.d << c << utils::io::endl;
|
||||
}
|
||||
|
||||
inline ~CommandBase() noexcept = default;
|
||||
|
||||
private:
|
||||
Execute mExecute;
|
||||
std::array<intptr_t, 16> mCallstack = {0};
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -218,6 +236,7 @@ public:
|
||||
using Cmd = COMMAND_TYPE(methodName); \
|
||||
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
||||
new(p) Cmd(mDispatcher.methodName##_, APPLY(std::move, params)); \
|
||||
((Cmd*)p)->captureCallstack(); \
|
||||
DEBUG_COMMAND_END(methodName, false); \
|
||||
}
|
||||
|
||||
@@ -237,6 +256,7 @@ public:
|
||||
using Cmd = COMMAND_TYPE(methodName##R); \
|
||||
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
||||
new(p) Cmd(mDispatcher.methodName##_, RetType(result), APPLY(std::move, params)); \
|
||||
((Cmd*)p)->captureCallstack(); \
|
||||
DEBUG_COMMAND_END(methodName, false); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ template<typename T>
|
||||
class ConcreteDispatcher;
|
||||
class Dispatcher;
|
||||
class CommandStream;
|
||||
class CommandBase;
|
||||
|
||||
class Driver {
|
||||
public:
|
||||
@@ -83,6 +84,8 @@ public:
|
||||
virtual void debugCommandEnd(CommandStream* cmds,
|
||||
bool synchronous, const char* methodName) noexcept = 0;
|
||||
|
||||
CommandBase* mCurrentExecutingCommand = nullptr;
|
||||
|
||||
/*
|
||||
* Asynchronous calls here only to provide a type to CommandStream. They must be non-virtual
|
||||
* so that calling the concrete implementation won't go through a vtable.
|
||||
|
||||
@@ -26,6 +26,17 @@ Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
|
||||
|
||||
Program::Program(Program&& rhs) noexcept = default;
|
||||
|
||||
Program& Program::operator=(Program&& rhs) noexcept {
|
||||
mUniformBlocks.operator=(rhs.mUniformBlocks);
|
||||
mSamplerGroups.operator=(std::move(rhs.mSamplerGroups));
|
||||
mShadersSource.operator=(std::move(rhs.mShadersSource));
|
||||
mName.operator=(std::move(rhs.mName));
|
||||
mLogger.operator=(std::move(rhs.mLogger));
|
||||
mSpecializationConstants.operator=(std::move(rhs.mSpecializationConstants));
|
||||
mBindingUniformInfo.operator=(std::move(rhs.mBindingUniformInfo));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program::~Program() noexcept = default;
|
||||
|
||||
Program& Program::priorityQueue(CompilerPriorityQueue priorityQueue) noexcept {
|
||||
|
||||
@@ -345,7 +345,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
auto colorTexture = handle_cast<MetalTexture>(buffer.handle);
|
||||
ASSERT_PRECONDITION(colorTexture->getMtlTextureForWrite(),
|
||||
"Color texture passed to render target has no texture allocation");
|
||||
colorTexture->extendLodRangeTo(buffer.level);
|
||||
colorTexture->updateLodRange(buffer.level);
|
||||
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
auto depthTexture = handle_cast<MetalTexture>(depth.handle);
|
||||
ASSERT_PRECONDITION(depthTexture->getMtlTextureForWrite(),
|
||||
"Depth texture passed to render target has no texture allocation.");
|
||||
depthTexture->extendLodRangeTo(depth.level);
|
||||
depthTexture->updateLodRange(depth.level);
|
||||
depthAttachment = { depthTexture, depth.level, depth.layer };
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
auto stencilTexture = handle_cast<MetalTexture>(stencil.handle);
|
||||
ASSERT_PRECONDITION(stencilTexture->getMtlTextureForWrite(),
|
||||
"Stencil texture passed to render target has no texture allocation.");
|
||||
stencilTexture->extendLodRangeTo(stencil.level);
|
||||
stencilTexture->updateLodRange(stencil.level);
|
||||
stencilAttachment = { stencilTexture, stencil.level, stencil.layer };
|
||||
}
|
||||
|
||||
@@ -789,8 +789,6 @@ void MetalDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t ind
|
||||
}
|
||||
|
||||
void MetalDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
|
||||
auto tex = handle_cast<MetalTexture>(th);
|
||||
tex->setLodRange(minLevel, maxLevel);
|
||||
}
|
||||
|
||||
void MetalDriver::update3DImage(Handle<HwTexture> th, uint32_t level,
|
||||
@@ -902,24 +900,29 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
|
||||
// 2. LOD-clamped textures
|
||||
//
|
||||
// Both of these cases prevent us from knowing the final id<MTLTexture> that will be bound into
|
||||
// the argument buffer representing the sampler group. So, we wait until draw call time to bind
|
||||
// textures (done in finalizeSamplerGroup).
|
||||
// the argument buffer representing the sampler group. So, we bind what we can now and wait
|
||||
// until draw call time to bind any special cases (done in finalizeSamplerGroup).
|
||||
// The good news is that once a render pass has started, the texture bindings won't change.
|
||||
// A SamplerGroup is "finalized" when all of its textures have been set and is ready for use in
|
||||
// a draw call.
|
||||
// finalizeSamplerGroup has one additional responsibility: to call useResources for all the
|
||||
// textures, which is required by Metal.
|
||||
// Even if we do know all the final textures at this point, we still wait until draw call time
|
||||
// to call finalizeSamplerGroup, which has one additional responsibility: to call useResources
|
||||
// for all the textures, which is required by Metal.
|
||||
for (size_t s = 0; s < data.size / sizeof(SamplerDescriptor); s++) {
|
||||
if (!samplers[s].t) {
|
||||
// Assign a default sampler to empty slots.
|
||||
// Assign a default texture / sampler to empty slots.
|
||||
// Metal requires all samplers referenced in shaders to be bound.
|
||||
// An empty texture will be assigned inside finalizeSamplerGroup.
|
||||
id<MTLTexture> empty = getOrCreateEmptyTexture(mContext);
|
||||
sb->setFinalizedTexture(s, empty);
|
||||
|
||||
id<MTLSamplerState> sampler = mContext->samplerStateCache.getOrCreateState({});
|
||||
sb->setFinalizedSampler(s, sampler);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bind the sampler state. We always know the full sampler state at updateSamplerGroup time.
|
||||
// First, bind the sampler state. We always know the full sampler state at
|
||||
// updateSamplerGroup time.
|
||||
SamplerState samplerState {
|
||||
.samplerParams = samplers[s].s,
|
||||
};
|
||||
@@ -927,6 +930,27 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
|
||||
sb->setFinalizedSampler(s, sampler);
|
||||
|
||||
sb->setTextureHandle(s, samplers[s].t);
|
||||
|
||||
auto* t = handle_cast<MetalTexture>(samplers[s].t);
|
||||
assert_invariant(t);
|
||||
|
||||
// If this texture is an external texture, we defer binding the texture until draw call time
|
||||
// (in finalizeSamplerGroup).
|
||||
if (t->target == SamplerType::SAMPLER_EXTERNAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!t->allLodsValid()) {
|
||||
// The texture doesn't have all of its LODs loaded, and this could change by the time we
|
||||
// issue a draw call with this sampler group. So, we defer binding the texture until
|
||||
// draw call time (in finalizeSamplerGroup).
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we get here, we know we have a valid MTLTexture that's guaranteed not to change.
|
||||
id<MTLTexture> mtlTexture = t->getMtlTextureForRead();
|
||||
assert_invariant(mtlTexture);
|
||||
sb->setFinalizedTexture(s, mtlTexture);
|
||||
}
|
||||
|
||||
scheduleDestroy(std::move(data));
|
||||
@@ -1352,8 +1376,8 @@ void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
}
|
||||
|
||||
void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
// All the id<MTLSamplerState> objects have already been bound to the argument buffer.
|
||||
// Here we bind all the textures.
|
||||
// All of the id<MTLSamplerState> objects have already been bound to the argument buffer.
|
||||
// Here we bind any textures that were unable to be bound in updateSamplerGroup.
|
||||
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(mContext);
|
||||
|
||||
@@ -1375,38 +1399,28 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
}
|
||||
#endif
|
||||
|
||||
utils::FixedCapacityVector<id<MTLTexture>> newTextures(samplerGroup->size, nil);
|
||||
for (size_t binding = 0; binding < samplerGroup->size; binding++) {
|
||||
auto [th, _] = samplerGroup->getFinalizedTexture(binding);
|
||||
auto [th, t] = samplerGroup->getFinalizedTexture(binding);
|
||||
|
||||
if (!th) {
|
||||
// Bind an empty texture.
|
||||
newTextures[binding] = getOrCreateEmptyTexture(mContext);
|
||||
// This may be an external texture, in which case we can't cache the id<MTLTexture>, we
|
||||
// need to refetch it in case the external image has changed.
|
||||
bool isExternalImage = false;
|
||||
if (th) {
|
||||
auto* texture = handle_cast<MetalTexture>(th);
|
||||
isExternalImage = texture->target == SamplerType::SAMPLER_EXTERNAL;
|
||||
}
|
||||
|
||||
// If t is non-nil, then we've already finalized this texture.
|
||||
if (t && !isExternalImage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's possible that some texture handles are null, but we should have already handled
|
||||
// these inside updateSamplerGroup by binding an "empty" texture.
|
||||
assert_invariant(th);
|
||||
auto* texture = handle_cast<MetalTexture>(th);
|
||||
|
||||
// External images
|
||||
if (texture->target == SamplerType::SAMPLER_EXTERNAL) {
|
||||
if (texture->externalImage.isValid()) {
|
||||
id<MTLTexture> mtlTexture = texture->externalImage.getMetalTextureForDraw();
|
||||
assert_invariant(mtlTexture);
|
||||
newTextures[binding] = mtlTexture;
|
||||
} else {
|
||||
// Bind an empty texture.
|
||||
newTextures[binding] = getOrCreateEmptyTexture(mContext);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
newTextures[binding] = texture->getMtlTextureForRead();
|
||||
}
|
||||
|
||||
if (!std::equal(newTextures.begin(), newTextures.end(), samplerGroup->textures.begin())) {
|
||||
// One or more of the id<MTLTexture>s has changed.
|
||||
// First, determine if this SamplerGroup needs mutation.
|
||||
// Determine if this SamplerGroup needs mutation.
|
||||
// We can't just simply mutate the SamplerGroup, since it could currently be in use by the
|
||||
// GPU from a prior render pass.
|
||||
// If the SamplerGroup does need mutation, then there's two cases:
|
||||
@@ -1414,18 +1428,30 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
// draw call). We're free to mutate it.
|
||||
// 2. The SamplerGroup is finalized. We must call mutate(), which will create a new argument
|
||||
// buffer that we can then mutate freely.
|
||||
// TODO: don't just always call mutate, check to see if the texture is actually different.
|
||||
|
||||
if (samplerGroup->isFinalized()) {
|
||||
samplerGroup->mutate(cmdBuffer);
|
||||
}
|
||||
|
||||
for (size_t binding = 0; binding < samplerGroup->size; binding++) {
|
||||
samplerGroup->setFinalizedTexture(binding, newTextures[binding]);
|
||||
// External images
|
||||
if (texture->target == SamplerType::SAMPLER_EXTERNAL) {
|
||||
if (texture->externalImage.isValid()) {
|
||||
id<MTLTexture> mtlTexture = texture->externalImage.getMetalTextureForDraw();
|
||||
assert_invariant(mtlTexture);
|
||||
samplerGroup->setFinalizedTexture(binding, mtlTexture);
|
||||
} else {
|
||||
// Bind an empty texture.
|
||||
samplerGroup->setFinalizedTexture(binding, getOrCreateEmptyTexture(mContext));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
samplerGroup->finalize();
|
||||
samplerGroup->setFinalizedTexture(binding, texture->getMtlTextureForRead());
|
||||
}
|
||||
|
||||
samplerGroup->finalize();
|
||||
|
||||
// At this point, all the id<MTLTextures> should be set to valid textures. Some of them will be
|
||||
// the "empty" texture. Per Apple documentation, the useResource method must be called once per
|
||||
// render pass.
|
||||
|
||||
@@ -217,14 +217,21 @@ public:
|
||||
void generateMipmaps() noexcept;
|
||||
|
||||
// A texture starts out with none of its mip levels (also referred to as LODs) available for
|
||||
// reading. 4 actions update the range of LODs available:
|
||||
// reading. 3 actions update the range of LODs available:
|
||||
// - calling loadImage
|
||||
// - calling generateMipmaps
|
||||
// - using the texture as a render target attachment
|
||||
// - calling setMinMaxLevels
|
||||
// The range of available mips can only increase, never decrease.
|
||||
// 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 updateLodRange(uint32_t level);
|
||||
void updateLodRange(uint32_t minLevel, uint32_t maxLevel);
|
||||
|
||||
// Returns true if the texture has all of its mip levels accessible for reading.
|
||||
// For any MetalTexture, once this is true, will always return true.
|
||||
// The value returned will remain consistent for an entire render pass.
|
||||
bool allLodsValid() const {
|
||||
return minLod == 0 && maxLod == levels - 1;
|
||||
}
|
||||
|
||||
static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format);
|
||||
|
||||
|
||||
@@ -513,7 +513,7 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context),
|
||||
externalImage(context) {
|
||||
texture = metalTexture;
|
||||
setLodRange(0, levels - 1);
|
||||
updateLodRange(0, levels - 1);
|
||||
}
|
||||
|
||||
MetalTexture::~MetalTexture() {
|
||||
@@ -658,14 +658,14 @@ void MetalTexture::loadImage(uint32_t level, MTLRegion region, PixelBufferDescri
|
||||
}
|
||||
}
|
||||
|
||||
extendLodRangeTo(level);
|
||||
updateLodRange(level);
|
||||
}
|
||||
|
||||
void MetalTexture::generateMipmaps() noexcept {
|
||||
id <MTLBlitCommandEncoder> blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder];
|
||||
[blitEncoder generateMipmapsForTexture:texture];
|
||||
[blitEncoder endEncoding];
|
||||
setLodRange(0, texture.mipmapLevelCount - 1);
|
||||
updateLodRange(0, texture.mipmapLevelCount - 1);
|
||||
}
|
||||
|
||||
void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice,
|
||||
@@ -788,18 +788,18 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit");
|
||||
}
|
||||
|
||||
void MetalTexture::extendLodRangeTo(uint32_t level) {
|
||||
void MetalTexture::updateLodRange(uint32_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::updateLodRange(uint32_t min, uint32_t max) {
|
||||
assert_invariant(!isInRenderPass(&context));
|
||||
assert_invariant(min <= max);
|
||||
minLod = min;
|
||||
maxLod = max;
|
||||
minLod = std::min(minLod, min);
|
||||
maxLod = std::max(maxLod, max);
|
||||
lodTextureView = nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,11 +103,6 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
return programData;
|
||||
}
|
||||
|
||||
void wait() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
cond.wait(l, [this](){ return signaled; });
|
||||
}
|
||||
|
||||
// Checks if the programBinary is ready.
|
||||
// This is similar to std::future::wait_for(0s)
|
||||
bool isReady() const noexcept {
|
||||
@@ -341,23 +336,7 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
bool canceled = token->compiler.cancelTickOp(token);
|
||||
|
||||
if (token->compiler.mShaderCompilerThreadCount) {
|
||||
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
|
||||
// race.
|
||||
token->wait();
|
||||
} else {
|
||||
// The job has not been executed, but we still need to inform the callback manager in
|
||||
// order for future callbacks to be successfully called.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
} else if (canceled) {
|
||||
// Since the tick op was canceled, we need to .put the token here.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
token->compiler.cancelTickOp(token);
|
||||
|
||||
for (GLuint& shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
@@ -683,7 +662,7 @@ void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
|
||||
}
|
||||
|
||||
bool ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
||||
void ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
||||
// We do a linear search here, but this is rare, and we know the list is pretty small.
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
|
||||
@@ -691,11 +670,9 @@ bool ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
||||
});
|
||||
if (pos != ops.end()) {
|
||||
ops.erase(pos);
|
||||
return true;
|
||||
}
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::executeTickOps() noexcept {
|
||||
|
||||
@@ -141,7 +141,7 @@ private:
|
||||
void runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept;
|
||||
void executeTickOps() noexcept;
|
||||
bool cancelTickOp(program_token_t token) noexcept;
|
||||
void cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#include "generated/vkshaders/vkshaders.h"
|
||||
|
||||
#define FILAMENT_VULKAN_CHECK_BLIT_FORMAT 0
|
||||
|
||||
using namespace bluevk;
|
||||
using namespace utils;
|
||||
|
||||
@@ -67,7 +69,7 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
if constexpr (FILAMENT_VULKAN_VERBOSE) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< "layout=" << src.getLayout()
|
||||
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
|
||||
@@ -130,7 +132,7 @@ void VulkanBlitter::blitColor(BlitArgs args) {
|
||||
const VulkanAttachment dst = args.dstTarget->getColor(0);
|
||||
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
#if FILAMENT_VULKAN_CHECK_BLIT_FORMAT
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
@@ -145,7 +147,7 @@ void VulkanBlitter::blitColor(BlitArgs args) {
|
||||
}
|
||||
#endif
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
|
||||
@@ -158,7 +160,7 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
const VulkanAttachment dst = args.dstTarget->getDepth();
|
||||
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
#if FILAMENT_VULKAN_CHECK_BLIT_FORMAT
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
@@ -182,7 +184,7 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
@@ -195,11 +197,13 @@ void VulkanBlitter::terminate() noexcept {
|
||||
mDepthResolveProgram = nullptr;
|
||||
|
||||
if (mTriangleBuffer) {
|
||||
mTriangleBuffer->terminate();
|
||||
delete mTriangleBuffer;
|
||||
mTriangleBuffer = nullptr;
|
||||
}
|
||||
|
||||
if (mParamsBuffer) {
|
||||
mParamsBuffer->terminate();
|
||||
delete mParamsBuffer;
|
||||
mParamsBuffer = nullptr;
|
||||
}
|
||||
@@ -239,7 +243,7 @@ void VulkanBlitter::lazyInit() noexcept {
|
||||
mDepthResolveProgram->samplerGroupInfo[0].samplers.reserve(1);
|
||||
mDepthResolveProgram->samplerGroupInfo[0].samplers.resize(1);
|
||||
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
if constexpr (FILAMENT_VULKAN_VERBOSE) {
|
||||
utils::slog.d << "Created Shader Module for VulkanBlitter "
|
||||
<< "shaders = (" << vertexShader << ", " << fragmentShader << ")"
|
||||
<< utils::io::endl;
|
||||
@@ -253,7 +257,7 @@ void VulkanBlitter::lazyInit() noexcept {
|
||||
};
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
|
||||
mTriangleBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
sizeof(kTriangleVertices));
|
||||
@@ -274,7 +278,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
||||
lazyInit();
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
|
||||
commands->acquire(src.texture);
|
||||
commands->acquire(dst.texture);
|
||||
|
||||
|
||||
@@ -45,7 +45,14 @@ VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
}
|
||||
|
||||
VulkanBuffer::~VulkanBuffer() {
|
||||
assert_invariant(mGpuMemory == VK_NULL_HANDLE);
|
||||
assert_invariant(mGpuBuffer == VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
void VulkanBuffer::terminate() {
|
||||
vmaDestroyBuffer(mAllocator, mGpuBuffer, mGpuMemory);
|
||||
mGpuMemory = VK_NULL_HANDLE;
|
||||
mGpuBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, VkBufferUsageFlags usage,
|
||||
uint32_t numBytes);
|
||||
~VulkanBuffer();
|
||||
void terminate();
|
||||
void loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
|
||||
uint32_t numBytes) const;
|
||||
VkBuffer getGpuBuffer() const {
|
||||
|
||||
@@ -33,53 +33,43 @@ using namespace utils;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
using Timestamp = VulkanGroupMarkers::Timestamp;
|
||||
#endif
|
||||
|
||||
VulkanCmdFence::VulkanCmdFence(VkFence ifence)
|
||||
: fence(ifence) {
|
||||
VulkanCmdFence::VulkanCmdFence(VkDevice device, bool signaled) : device(device) {
|
||||
VkFenceCreateInfo fenceCreateInfo { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
if (signaled) {
|
||||
fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
}
|
||||
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
|
||||
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence gets
|
||||
// submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually finishes
|
||||
// executing the command buffer, the status changes to VK_SUCCESS.
|
||||
status.store(VK_INCOMPLETE);
|
||||
}
|
||||
|
||||
VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device,
|
||||
VkCommandPool pool)
|
||||
: mResourceManager(allocator) {
|
||||
// Create the low-level command buffer.
|
||||
const VkCommandBufferAllocateInfo allocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
|
||||
// The buffer allocated here will be implicitly reset when vkBeginCommandBuffer is called.
|
||||
// We don't need to deallocate since destroying the pool will free all of the buffers.
|
||||
vkAllocateCommandBuffers(device, &allocateInfo, &mBuffer);
|
||||
VulkanCmdFence::~VulkanCmdFence() {
|
||||
vkDestroyFence(device, fence, VKALLOC);
|
||||
}
|
||||
|
||||
CommandBufferObserver::~CommandBufferObserver() {}
|
||||
|
||||
static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) {
|
||||
VkCommandPoolCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
|
||||
| VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
|
||||
.queueFamilyIndex = queueFamilyIndex,
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags =
|
||||
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
|
||||
.queueFamilyIndex = queueFamilyIndex,
|
||||
};
|
||||
VkCommandPool pool;
|
||||
vkCreateCommandPool(device, &createInfo, VKALLOC, &pool);
|
||||
return pool;
|
||||
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
|
||||
mMarkers.push_back(marker);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
mTimestamps.push_back(start.time_since_epoch().count() > 0.0
|
||||
? start
|
||||
: std::chrono::high_resolution_clock::now());
|
||||
@@ -90,7 +80,7 @@ std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
|
||||
auto const marker = mMarkers.back();
|
||||
mMarkers.pop_back();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const timestamp = mTimestamps.back();
|
||||
mTimestamps.pop_back();
|
||||
return std::make_pair(marker, timestamp);
|
||||
@@ -103,7 +93,7 @@ std::pair<std::string, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
|
||||
auto const marker = mMarkers.front();
|
||||
mMarkers.pop_front();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const timestamp = mTimestamps.front();
|
||||
mTimestamps.pop_front();
|
||||
return std::make_pair(marker, timestamp);
|
||||
@@ -115,8 +105,8 @@ std::pair<std::string, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
|
||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
|
||||
assert_invariant(!empty());
|
||||
auto const marker = mMarkers.back();
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
auto const topTimestamp = mTimestamps.front();
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const topTimestamp = mTimestamps.top();
|
||||
return std::make_pair(marker, topTimestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
@@ -127,8 +117,6 @@ bool VulkanGroupMarkers::empty() const noexcept {
|
||||
return mMarkers.empty();
|
||||
}
|
||||
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VulkanContext* context, VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
@@ -141,30 +129,18 @@ VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFam
|
||||
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
|
||||
}
|
||||
|
||||
VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
for (auto& fence: mFences) {
|
||||
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator, mDevice, mPool);
|
||||
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator);
|
||||
}
|
||||
|
||||
#if !FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
(void) mContext;
|
||||
#endif
|
||||
}
|
||||
|
||||
VulkanCommands::~VulkanCommands() {
|
||||
wait();
|
||||
gc();
|
||||
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
|
||||
for (VkSemaphore sema: mSubmissionSignals) {
|
||||
for (VkSemaphore sema : mSubmissionSignals) {
|
||||
vkDestroySemaphore(mDevice, sema, VKALLOC);
|
||||
}
|
||||
for (VkFence fence: mFences) {
|
||||
vkDestroyFence(mDevice, fence, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& VulkanCommands::get() {
|
||||
@@ -175,11 +151,11 @@ VulkanCommandBuffer& VulkanCommands::get() {
|
||||
// If we ran out of available command buffers, stall until one finishes. This is very rare.
|
||||
// It occurs only when Filament invokes commit() or endFrame() a large number of times without
|
||||
// presenting the swap chain or waiting on a fence.
|
||||
while (mAvailableBufferCount == 0) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
slog.i << "VulkanCommands has stalled. "
|
||||
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
|
||||
<< io::endl;
|
||||
while (mAvailableCount == 0) {
|
||||
#if VK_REPORT_STALLS
|
||||
slog.i << "VulkanCommands has stalled. "
|
||||
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
|
||||
<< io::endl;
|
||||
#endif
|
||||
wait();
|
||||
gc();
|
||||
@@ -189,7 +165,7 @@ VulkanCommandBuffer& VulkanCommands::get() {
|
||||
// Find an available slot.
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() == VK_NULL_HANDLE) {
|
||||
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||
mCurrentCommandBufferIndex = static_cast<int8_t>(i);
|
||||
currentbuf = wrapper;
|
||||
break;
|
||||
@@ -197,33 +173,41 @@ VulkanCommandBuffer& VulkanCommands::get() {
|
||||
}
|
||||
|
||||
assert_invariant(currentbuf);
|
||||
mAvailableBufferCount--;
|
||||
--mAvailableCount;
|
||||
|
||||
// Create the low-level command buffer.
|
||||
const VkCommandBufferAllocateInfo allocateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = mPool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1
|
||||
};
|
||||
vkAllocateCommandBuffers(mDevice, &allocateInfo, ¤tbuf->cmdbuffer);
|
||||
|
||||
// Note that the fence wrapper uses shared_ptr because a DriverAPI fence can also have ownership
|
||||
// over it. The destruction of the low-level fence occurs either in VulkanCommands::gc(), or in
|
||||
// VulkanDriver::destroyFence(), both of which are safe spots.
|
||||
currentbuf->fence = std::make_shared<VulkanCmdFence>(mFences[mCurrentCommandBufferIndex]);
|
||||
currentbuf->fence = std::make_shared<VulkanCmdFence>(mDevice);
|
||||
|
||||
// Begin writing into the command buffer.
|
||||
const VkCommandBufferBeginInfo binfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
const VkCommandBufferBeginInfo binfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
vkBeginCommandBuffer(currentbuf->buffer(), &binfo);
|
||||
vkBeginCommandBuffer(currentbuf->cmdbuffer, &binfo);
|
||||
|
||||
// Notify the observer that a new command buffer has been activated.
|
||||
if (mObserver) {
|
||||
mObserver->onCommandBuffer(*currentbuf);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
// We push the current markers onto a temporary stack. This must be placed after currentbuf is
|
||||
// set to the new command buffer since pushGroupMarker also calls get().
|
||||
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||
auto [marker, time] = mCarriedOverMarkers->pop();
|
||||
pushGroupMarker(marker.c_str(), time);
|
||||
}
|
||||
#endif
|
||||
|
||||
return *currentbuf;
|
||||
}
|
||||
|
||||
@@ -233,25 +217,11 @@ bool VulkanCommands::flush() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Before actually submitting, we need to pop any leftover group markers.
|
||||
// Note that this needs to occur before vkEndCommandBuffer.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
while (mGroupMarkers && !mGroupMarkers->empty()) {
|
||||
if (!mCarriedOverMarkers) {
|
||||
mCarriedOverMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
}
|
||||
auto const [marker, time] = mGroupMarkers->top();
|
||||
mCarriedOverMarkers->push(marker, time);
|
||||
// We still need to call through to vkCmdEndDebugUtilsLabelEXT.
|
||||
popGroupMarker();
|
||||
}
|
||||
#endif
|
||||
|
||||
int8_t const index = mCurrentCommandBufferIndex;
|
||||
VulkanCommandBuffer const* currentbuf = mStorage[index].get();
|
||||
VkSemaphore const renderingFinished = mSubmissionSignals[index];
|
||||
|
||||
vkEndCommandBuffer(currentbuf->buffer());
|
||||
vkEndCommandBuffer(currentbuf->cmdbuffer);
|
||||
|
||||
// If the injected semaphore is an "image available" semaphore that has not yet been signaled,
|
||||
// it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the
|
||||
@@ -260,26 +230,24 @@ bool VulkanCommands::flush() {
|
||||
// the only safe option because the previously submitted command buffer might have set up some
|
||||
// state that the new command buffer depends on.
|
||||
VkPipelineStageFlags waitDestStageMasks[2] = {
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
};
|
||||
|
||||
VkSemaphore signals[2] = {
|
||||
VK_NULL_HANDLE,
|
||||
VK_NULL_HANDLE,
|
||||
VK_NULL_HANDLE,
|
||||
VK_NULL_HANDLE,
|
||||
};
|
||||
|
||||
VkCommandBuffer const cmdbuffer = currentbuf->buffer();
|
||||
|
||||
VkSubmitInfo submitInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = 0,
|
||||
.pWaitSemaphores = signals,
|
||||
.pWaitDstStageMask = waitDestStageMasks,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmdbuffer,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &renderingFinished,
|
||||
VkSubmitInfo submitInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = 0,
|
||||
.pWaitSemaphores = signals,
|
||||
.pWaitDstStageMask = waitDestStageMasks,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = ¤tbuf->cmdbuffer,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &renderingFinished,
|
||||
};
|
||||
|
||||
if (mSubmissionSignal) {
|
||||
@@ -295,14 +263,24 @@ bool VulkanCommands::flush() {
|
||||
submitInfo.pWaitSemaphores = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
slog.i << "Submitting cmdbuffer=" << cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< " fence=" << currentbuf->fence->fence
|
||||
<< utils::io::endl;
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
slog.i << "Submitting cmdbuffer=" << currentbuf->cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< io::endl;
|
||||
#endif
|
||||
|
||||
// Before actually submitting, we need to pop any leftover group markers.
|
||||
while (mGroupMarkers && !mGroupMarkers->empty()) {
|
||||
if (!mCarriedOverMarkers) {
|
||||
mCarriedOverMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
}
|
||||
auto const [marker, time] = mGroupMarkers->top();
|
||||
mCarriedOverMarkers->push(marker, time);
|
||||
// We still need to call through to vkCmdEndDebugUtilsLabelEXT.
|
||||
popGroupMarker();
|
||||
}
|
||||
|
||||
auto& cmdfence = currentbuf->fence;
|
||||
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
|
||||
cmdfence->status.store(VK_NOT_READY);
|
||||
@@ -310,9 +288,9 @@ bool VulkanCommands::flush() {
|
||||
cmdfence->condition.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
if (result != VK_SUCCESS) {
|
||||
utils::slog.d << "Failed command buffer submission result: " << result << utils::io::endl;
|
||||
utils::slog.d <<"Failed command buffer submission result: " << result << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
@@ -326,8 +304,8 @@ bool VulkanCommands::flush() {
|
||||
VkSemaphore VulkanCommands::acquireFinishedSignal() {
|
||||
VkSemaphore semaphore = mSubmissionSignal;
|
||||
mSubmissionSignal = VK_NULL_HANDLE;
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||
#endif
|
||||
return semaphore;
|
||||
}
|
||||
@@ -335,7 +313,7 @@ VkSemaphore VulkanCommands::acquireFinishedSignal() {
|
||||
void VulkanCommands::injectDependency(VkSemaphore next) {
|
||||
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
|
||||
mInjectedSignal = next;
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
||||
#endif
|
||||
}
|
||||
@@ -345,49 +323,44 @@ void VulkanCommands::wait() {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() != VK_NULL_HANDLE
|
||||
if (wrapper->cmdbuffer != VK_NULL_HANDLE
|
||||
&& mCurrentCommandBufferIndex != static_cast<int8_t>(i)) {
|
||||
fences[count++] = wrapper->fence->fence;
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
|
||||
updateFences();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommands::gc() {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("commands::gc");
|
||||
|
||||
VkFence fences[CAPACITY];
|
||||
VkCommandBuffer buffers[CAPACITY];
|
||||
size_t count = 0;
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() == VK_NULL_HANDLE) {
|
||||
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
VkResult const result = vkGetFenceStatus(mDevice, wrapper->fence->fence);
|
||||
VkResult const result = vkWaitForFences(mDevice, 1, &wrapper->fence->fence, VK_TRUE, 0);
|
||||
if (result != VK_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
fences[count++] = wrapper->fence->fence;
|
||||
buffers[count++] = wrapper->cmdbuffer;
|
||||
wrapper->cmdbuffer = VK_NULL_HANDLE;
|
||||
wrapper->fence->status.store(VK_SUCCESS);
|
||||
wrapper->reset();
|
||||
mAvailableBufferCount++;
|
||||
wrapper->fence.reset();
|
||||
wrapper->clearResources();
|
||||
++mAvailableCount;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
vkResetFences(mDevice, count, fences);
|
||||
vkFreeCommandBuffers(mDevice, mPool, count, buffers);
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanCommands::updateFences() {
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->buffer() != VK_NULL_HANDLE) {
|
||||
if (wrapper->cmdbuffer != VK_NULL_HANDLE) {
|
||||
VulkanCmdFence* fence = wrapper->fence.get();
|
||||
if (fence) {
|
||||
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
|
||||
@@ -398,10 +371,8 @@ void VulkanCommands::updateFences() {
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
|
||||
void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
// If the timestamp is not 0, then we are carrying over a marker across buffer submits.
|
||||
// If it is 0, then this is a normal marker push and we should just print debug line as usual.
|
||||
if (timestamp.time_since_epoch().count() == 0.0) {
|
||||
@@ -410,7 +381,7 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
|
||||
#endif
|
||||
|
||||
// TODO: Add group marker color to the Driver API
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
const VkCommandBuffer cmdbuffer = get().cmdbuffer;
|
||||
|
||||
if (!mGroupMarkers) {
|
||||
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
@@ -435,19 +406,20 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
|
||||
}
|
||||
|
||||
void VulkanCommands::popGroupMarker() {
|
||||
assert_invariant(mGroupMarkers);
|
||||
assert_invariant(mGroupMarkers);
|
||||
|
||||
if (!mGroupMarkers->empty()) {
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
auto const [marker, startTime] = mGroupMarkers->pop();
|
||||
auto const endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> diff = endTime - startTime;
|
||||
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms\n"
|
||||
<< utils::io::flush;
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
#endif
|
||||
const VkCommandBuffer cmdbuffer = get().cmdbuffer;
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const [marker, startTime] = mGroupMarkers->pop();
|
||||
auto const endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> diff = endTime - startTime;
|
||||
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms\n"
|
||||
<< utils::io::flush;
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
#endif
|
||||
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
@@ -462,7 +434,7 @@ void VulkanCommands::popGroupMarker() {
|
||||
}
|
||||
|
||||
void VulkanCommands::insertEventMarker(char const* string, uint32_t len) {
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
VkCommandBuffer const cmdbuffer = get().cmdbuffer;
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
@@ -472,9 +444,9 @@ void VulkanCommands::insertEventMarker(char const* string, uint32_t len) {
|
||||
vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = string,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = string,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo);
|
||||
}
|
||||
@@ -486,9 +458,8 @@ std::string VulkanCommands::getTopGroupMarker() const {
|
||||
}
|
||||
return std::get<0>(mGroupMarkers->top());
|
||||
}
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
} // namespace filament::backend
|
||||
}// namespace filament::backend
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning( pop )
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace filament::backend {
|
||||
|
||||
struct VulkanContext;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
class VulkanGroupMarkers {
|
||||
public:
|
||||
using Timestamp = std::chrono::time_point<std::chrono::high_resolution_clock>;
|
||||
@@ -52,17 +51,16 @@ public:
|
||||
|
||||
private:
|
||||
std::list<std::string> mMarkers;
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
std::list<Timestamp> mTimestamps;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
|
||||
struct VulkanCmdFence {
|
||||
VulkanCmdFence(VkFence ifence);
|
||||
~VulkanCmdFence() = default;
|
||||
VulkanCmdFence(VkDevice device, bool signaled = false);
|
||||
~VulkanCmdFence();
|
||||
const VkDevice device;
|
||||
VkFence fence;
|
||||
utils::Condition condition;
|
||||
utils::Mutex mutex;
|
||||
@@ -73,10 +71,13 @@ struct VulkanCmdFence {
|
||||
// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and
|
||||
// we're done waiting on the most recent submission of the given command buffer.
|
||||
struct VulkanCommandBuffer {
|
||||
VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, VkCommandPool pool);
|
||||
VulkanCommandBuffer(VulkanResourceAllocator* allocator)
|
||||
: mResourceManager(allocator) {}
|
||||
|
||||
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
|
||||
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
|
||||
VkCommandBuffer cmdbuffer = VK_NULL_HANDLE;
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
|
||||
inline void acquire(VulkanResource* resource) {
|
||||
mResourceManager.acquire(resource);
|
||||
@@ -86,23 +87,12 @@ struct VulkanCommandBuffer {
|
||||
mResourceManager.acquire(srcResources);
|
||||
}
|
||||
|
||||
inline void reset() {
|
||||
fence.reset();
|
||||
inline void clearResources() {
|
||||
mResourceManager.clear();
|
||||
}
|
||||
|
||||
inline VkCommandBuffer buffer() const {
|
||||
if (fence) {
|
||||
return mBuffer;
|
||||
}
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
|
||||
private:
|
||||
VulkanAcquireOnlyResourceManager mResourceManager;
|
||||
VkCommandBuffer mBuffer;
|
||||
};
|
||||
|
||||
// Allows classes to be notified after a new command buffer has been activated.
|
||||
@@ -173,7 +163,6 @@ class VulkanCommands {
|
||||
// The observer's event handler can only be called during get().
|
||||
void setObserver(CommandBufferObserver* observer) { mObserver = observer; }
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {});
|
||||
|
||||
void popGroupMarker();
|
||||
@@ -181,10 +170,9 @@ class VulkanCommands {
|
||||
void insertEventMarker(char const* string, uint32_t len);
|
||||
|
||||
std::string getTopGroupMarker() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
|
||||
static constexpr int CAPACITY = VK_MAX_COMMAND_BUFFERS;
|
||||
VkDevice const mDevice;
|
||||
VkQueue const mQueue;
|
||||
VkCommandPool const mPool;
|
||||
@@ -196,15 +184,12 @@ class VulkanCommands {
|
||||
VkSemaphore mSubmissionSignal = {};
|
||||
VkSemaphore mInjectedSignal = {};
|
||||
utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>> mStorage;
|
||||
VkFence mFences[CAPACITY] = {};
|
||||
VkSemaphore mSubmissionSignals[CAPACITY] = {};
|
||||
uint8_t mAvailableBufferCount = CAPACITY;
|
||||
size_t mAvailableCount = CAPACITY;
|
||||
CommandBufferObserver* mObserver = nullptr;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
|
||||
std::unique_ptr<VulkanGroupMarkers> mCarriedOverMarkers;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define FILAMENT_VULKAN_VERBOSE 0
|
||||
#define FILAMENT_VULKAN_DUMP_API 0
|
||||
|
||||
#ifndef FILAMENT_VULKAN_HANDLE_ARENA_SIZE_IN_MB
|
||||
#define FILAMENT_VULKAN_HANDLE_ARENA_SIZE_IN_MB 8
|
||||
#endif
|
||||
|
||||
// In debug builds, we enable validation layers and set up a debug callback.
|
||||
//
|
||||
// To enable validation layers in Android, also be sure to set the jniLibs property in the gradle
|
||||
@@ -36,80 +43,21 @@
|
||||
// ndkVersion "23.1.7779620"
|
||||
//
|
||||
// Also consider changing the root `gradle.properties` to point to a debug build, although this is
|
||||
// not required for validation.
|
||||
|
||||
// FVK is short for Filament Vulkan
|
||||
#define FVK_DEBUG_SYSTRACE 0x00000001
|
||||
#define FVK_DEBUG_GROUP_MARKERS 0x00000002
|
||||
#define FVK_DEBUG_TEXTURE 0x00000004
|
||||
#define FVK_DEBUG_LAYOUT_TRANSITION 0x00000008
|
||||
#define FVK_DEBUG_COMMAND_BUFFER 0x00000010
|
||||
#define FVK_DEBUG_DUMP_API 0x00000020
|
||||
#define FVK_DEBUG_VALIDATION 0x00000040
|
||||
#define FVK_DEBUG_PRINT_GROUP_MARKERS 0x00000080
|
||||
#define FVK_DEBUG_BLIT_FORMAT 0x00000100
|
||||
#define FVK_DEBUG_BLITTER 0x00000200
|
||||
#define FVK_DEBUG_FBO_CACHE 0x00000400
|
||||
#define FVK_DEBUG_SHADER_MODULE 0x00000800
|
||||
#define FVK_DEBUG_READ_PIXELS 0x00001000
|
||||
#define FVK_DEBUG_PIPELINE_CACHE 0x00002000
|
||||
#define FVK_DEBUG_ALLOCATION 0x00004000
|
||||
|
||||
// Usefaul default combinations
|
||||
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
|
||||
#define FVK_DEBUG_PERFORMANCE \
|
||||
FVK_DEBUG_SYSTRACE | \
|
||||
FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
#define FVK_DEBUG_CORRECTNESS \
|
||||
FVK_DEBUG_VALIDATION | \
|
||||
FVK_DEBUG_SHADER_MODULE | \
|
||||
FVK_DEBUG_TEXTURE | \
|
||||
FVK_DEBUG_LAYOUT_TRANSITION
|
||||
|
||||
#define FVK_DEBUG_RENDER_PASSES \
|
||||
FVK_DEBUG_GROUP_MARKERS | \
|
||||
FVK_DEBUG_PRINT_GROUP_MARKERS
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FVK_DEBUG_FLAGS FVK_DEBUG_PERFORMANCE
|
||||
// not required for validation if you change the definition of VK_ENABLE_VALIDATION below.
|
||||
#if defined(NDEBUG)
|
||||
#define VK_ENABLE_VALIDATION 0
|
||||
#else
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#define VK_ENABLE_VALIDATION 1
|
||||
#endif
|
||||
|
||||
#define FVK_ENABLED(flags) ((FVK_DEBUG_FLAGS) & (flags))
|
||||
#define FVK_ENABLED_BOOL(flags) ((bool) FVK_ENABLED(flags))
|
||||
|
||||
// Ensure dependencies are met between debug options
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
static_assert(FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS));
|
||||
#endif
|
||||
|
||||
// end dependcy checks
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
|
||||
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
|
||||
#else
|
||||
#define FVK_SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker)
|
||||
#define FVK_SYSTRACE_END()
|
||||
#endif
|
||||
|
||||
#ifndef FVK_HANDLE_ARENA_SIZE_IN_MB
|
||||
#define FVK_HANDLE_ARENA_SIZE_IN_MB 8
|
||||
#endif
|
||||
#define VK_REPORT_STALLS 0
|
||||
|
||||
// All vkCreate* functions take an optional allocator. For now we select the default allocator by
|
||||
// passing in a null pointer, and we highlight the argument by using the VKALLOC constant.
|
||||
constexpr struct VkAllocationCallbacks* VKALLOC = nullptr;
|
||||
|
||||
constexpr static const int FVK_REQUIRED_VERSION_MAJOR = 1;
|
||||
constexpr static const int FVK_REQUIRED_VERSION_MINOR = 1;
|
||||
constexpr static const int VK_REQUIRED_VERSION_MAJOR = 1;
|
||||
constexpr static const int VK_REQUIRED_VERSION_MINOR = 1;
|
||||
|
||||
// Maximum number of VkCommandBuffer handles managed simultaneously by VulkanCommands.
|
||||
//
|
||||
@@ -117,18 +65,18 @@ constexpr static const int FVK_REQUIRED_VERSION_MINOR = 1;
|
||||
// buffers that have been submitted but have not yet finished rendering. Note that Filament can
|
||||
// issue multiple commit calls in a single frame, and that we use a triple buffered swap chain on
|
||||
// some platforms.
|
||||
constexpr static const int FVK_MAX_COMMAND_BUFFERS = 10;
|
||||
constexpr static const int VK_MAX_COMMAND_BUFFERS = 10;
|
||||
|
||||
// Number of command buffer submissions that should occur before an unused pipeline is removed
|
||||
// from the cache.
|
||||
//
|
||||
// If this number is low, VkPipeline construction will occur frequently, which can
|
||||
// be extremely slow. If this number is high, the memory footprint will be large.
|
||||
constexpr static const int FVK_MAX_PIPELINE_AGE = 10;
|
||||
constexpr static const int VK_MAX_PIPELINE_AGE = 10;
|
||||
|
||||
// VulkanPipelineCache does not track which command buffers contain references to which pipelines,
|
||||
// instead it simply waits for at least FVK_MAX_COMMAND_BUFFERS submissions to occur before
|
||||
// instead it simply waits for at least VK_MAX_COMMAND_BUFFERS submissions to occur before
|
||||
// destroying any unused pipeline object.
|
||||
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
|
||||
static_assert(VK_MAX_PIPELINE_AGE >= VK_MAX_COMMAND_BUFFERS);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -117,9 +117,8 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery* query) {
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
|
||||
auto const cmdbuffer = commands->buffer();
|
||||
vkCmdResetQueryPool(cmdbuffer, mPool, index, 2);
|
||||
vkCmdWriteTimestamp(cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
vkCmdResetQueryPool(commands->cmdbuffer, mPool, index, 2);
|
||||
vkCmdWriteTimestamp(commands->cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
|
||||
// We stash this because getResult might come before the query is actually processed.
|
||||
query->setFence(commands->fence);
|
||||
@@ -128,7 +127,7 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
|
||||
void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery const* query) {
|
||||
uint32_t const index = query->getStoppingQueryIndex();
|
||||
vkCmdWriteTimestamp(commands->buffer(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
vkCmdWriteTimestamp(commands->cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
}
|
||||
|
||||
VulkanTimestamps::QueryResult VulkanTimestamps::getResult(VulkanTimerQuery const* query) {
|
||||
|
||||
@@ -30,9 +30,10 @@
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <set> // For VulkanDriver::debugCommandBegin
|
||||
#include <set>
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
@@ -100,7 +101,7 @@ VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevi
|
||||
return emptyTexture;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
#if VK_ENABLE_VALIDATION
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
|
||||
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
|
||||
int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) {
|
||||
@@ -135,7 +136,7 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
|
||||
utils::slog.e << utils::io::endl;
|
||||
return VK_FALSE;
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION)
|
||||
#endif// VK_ENABLE_VALIDATION
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -158,7 +159,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache),
|
||||
mReadPixels(mPlatform->getDevice()) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
#if VK_ENABLE_VALIDATION
|
||||
UTILS_UNUSED const PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback
|
||||
= vkCreateDebugReportCallbackEXT;
|
||||
VkResult result;
|
||||
@@ -213,7 +214,7 @@ UTILS_NOINLINE
|
||||
Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept {
|
||||
assert_invariant(platform);
|
||||
size_t defaultSize = FVK_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
size_t defaultSize = FILAMENT_VULKAN_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig{
|
||||
.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize)};
|
||||
return new VulkanDriver(platform, context, validConfig);
|
||||
@@ -265,15 +266,24 @@ void VulkanDriver::tick(int) {
|
||||
// rather than the wall clock, because we must wait 3 frames after a DriverAPI-level resource has
|
||||
// been destroyed for safe destruction, due to outstanding command buffers and triple buffering.
|
||||
void VulkanDriver::collectGarbage() {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("gc");
|
||||
// Command buffers need to be submitted and completed before other resources can be gc'd. And
|
||||
// its gc() function carrys out the *wait*.
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN("gc");
|
||||
|
||||
mCommands->gc();
|
||||
|
||||
SYSTRACE_NAME_BEGIN("stagepool-gc");
|
||||
mStagePool.gc();
|
||||
SYSTRACE_NAME_END();
|
||||
|
||||
SYSTRACE_NAME_BEGIN("framebuffercache-gc");
|
||||
mFramebufferCache.gc();
|
||||
FVK_SYSTRACE_END();
|
||||
SYSTRACE_NAME_END();
|
||||
|
||||
SYSTRACE_NAME_END(); // "gc"
|
||||
}
|
||||
|
||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
// Do nothing.
|
||||
}
|
||||
@@ -296,21 +306,14 @@ void VulkanDriver::endFrame(uint32_t frameId) {
|
||||
}
|
||||
|
||||
void VulkanDriver::flush(int) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("flush");
|
||||
mCommands->flush();
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::finish(int dummy) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("finish");
|
||||
|
||||
mCommands->flush();
|
||||
mCommands->wait();
|
||||
|
||||
mReadPixels.runUntilComplete();
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count) {
|
||||
@@ -523,22 +526,21 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
<< utils::io::endl;
|
||||
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
||||
}
|
||||
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
|
||||
mAllocator, mCommands.get(), mStagePool, nativeWindow, flags);
|
||||
mResourceManager.acquire(swapChain);
|
||||
mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
|
||||
mCommands.get(), mStagePool, nativeWindow, flags);
|
||||
}
|
||||
|
||||
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
|
||||
uint32_t height, uint64_t flags) {
|
||||
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
|
||||
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 &&
|
||||
!isSRGBSwapChainSupported()) {
|
||||
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
|
||||
<< utils::io::endl;
|
||||
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
||||
}
|
||||
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
|
||||
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
|
||||
mAllocator, mCommands.get(), mStagePool, nullptr, flags, VkExtent2D{width, height});
|
||||
mResourceManager.acquire(swapChain);
|
||||
mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
|
||||
mCommands.get(), mStagePool, nullptr, flags, VkExtent2D{width, height});
|
||||
}
|
||||
|
||||
void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
@@ -688,15 +690,16 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
|
||||
// When this fence gets submitted, its status changes to VK_NOT_READY.
|
||||
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
|
||||
if (cmdfence->status.load() == VK_SUCCESS) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
if (cmdfence->status.load() == VK_INCOMPLETE) {
|
||||
// This will obviously timeout if Filament creates a fence and immediately waits on it
|
||||
// without calling endFrame() or commit().
|
||||
cmdfence->condition.wait(lock);
|
||||
} else {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
// Two other states are possible:
|
||||
// - VK_INCOMPLETE: the corresponding buffer has not yet been submitted.
|
||||
// - VK_NOT_READY: the buffer has been submitted but not yet signaled.
|
||||
// In either case, we return TIMEOUT_EXPIRED to indicate the fence has not been signaled.
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
VkResult result =
|
||||
vkWaitForFences(mPlatform->getDevice(), 1, &cmdfence->fence, VK_TRUE, 0);
|
||||
return result == VK_SUCCESS ? FenceStatus::CONDITION_SATISFIED : FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
|
||||
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
|
||||
@@ -869,7 +872,7 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
auto ib = mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh);
|
||||
commands.acquire(ib);
|
||||
ib->buffer.loadFromCpu(commands.buffer(), p.buffer, byteOffset, p.size);
|
||||
ib->buffer.loadFromCpu(commands.cmdbuffer, p.buffer, byteOffset, p.size);
|
||||
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
@@ -880,7 +883,7 @@ void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescript
|
||||
|
||||
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
commands.acquire(bo);
|
||||
bo->buffer.loadFromCpu(commands.buffer(), bd.buffer, byteOffset, bd.size);
|
||||
bo->buffer.loadFromCpu(commands.cmdbuffer, bd.buffer, byteOffset, bd.size);
|
||||
|
||||
scheduleDestroy(std::move(bd));
|
||||
}
|
||||
@@ -891,7 +894,7 @@ void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
|
||||
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
commands.acquire(bo);
|
||||
// TODO: implement unsynchronized version
|
||||
bo->buffer.loadFromCpu(commands.buffer(), bd.buffer, byteOffset, bd.size);
|
||||
bo->buffer.loadFromCpu(commands.cmdbuffer, bd.buffer, byteOffset, bd.size);
|
||||
mResourceManager.acquire(bo);
|
||||
scheduleDestroy(std::move(bd));
|
||||
}
|
||||
@@ -987,9 +990,6 @@ void VulkanDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
}
|
||||
|
||||
void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("beginRenderPass");
|
||||
|
||||
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
|
||||
const VkExtent2D extent = rt->getExtent();
|
||||
assert_invariant(extent.width > 0 && extent.height > 0);
|
||||
@@ -1008,8 +1008,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
|
||||
VulkanAttachment depth = rt->getSamples() == 1 ? rt->getDepth() : rt->getMsaaDepth();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
if (depth.texture) {
|
||||
depth.texture->print();
|
||||
}
|
||||
@@ -1021,7 +1020,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
// the non-sampling case.
|
||||
bool samplingDepthAttachment = false;
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT;
|
||||
@@ -1154,7 +1153,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
|
||||
// Assign a label to the framebuffer for debugging purposes.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
if (UTILS_UNLIKELY(mContext.isDebugUtilsSupported())) {
|
||||
auto const topMarker = mCommands->getTopGroupMarker();
|
||||
if (!topMarker.empty()) {
|
||||
@@ -1168,7 +1166,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
vkSetDebugUtilsObjectNameEXT(mPlatform->getDevice(), &info);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// The current command buffer now owns a reference to the render target and its attachments.
|
||||
// Note that we must acquire parent textures, not sidecars.
|
||||
@@ -1245,15 +1242,11 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
.params = params,
|
||||
.currentSubpass = 0,
|
||||
};
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::endRenderPass(int) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("endRenderPass");
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands.buffer();
|
||||
VkCommandBuffer cmdbuffer = commands.cmdbuffer;
|
||||
vkCmdEndRenderPass(cmdbuffer);
|
||||
|
||||
VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
@@ -1295,7 +1288,6 @@ void VulkanDriver::endRenderPass(int) {
|
||||
}
|
||||
mCurrentRenderPass.renderTarget = nullptr;
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::nextSubpass(int) {
|
||||
@@ -1306,7 +1298,7 @@ void VulkanDriver::nextSubpass(int) {
|
||||
assert_invariant(renderTarget);
|
||||
assert_invariant(mCurrentRenderPass.params.subpassMask);
|
||||
|
||||
vkCmdNextSubpass(mCommands->get().buffer(), VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdNextSubpass(mCommands->get().cmdbuffer, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
|
||||
++mCurrentRenderPass.currentSubpass);
|
||||
@@ -1342,9 +1334,6 @@ void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
|
||||
}
|
||||
|
||||
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("makeCurrent");
|
||||
|
||||
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
|
||||
"Vulkan driver does not support distinct draw/read swap chains.");
|
||||
VulkanSwapChain* swapChain = mCurrentSwapChain
|
||||
@@ -1360,14 +1349,9 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
||||
if (UTILS_LIKELY(mDefaultRenderTarget)) {
|
||||
mDefaultRenderTarget->bindToSwapChain(*swapChain);
|
||||
}
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("commit");
|
||||
|
||||
VulkanSwapChain* swapChain = mResourceAllocator.handle_cast<VulkanSwapChain*>(sch);
|
||||
|
||||
if (mCommands->flush()) {
|
||||
@@ -1376,7 +1360,6 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||
|
||||
// Present the backbuffer after the most recent command buffer submission has finished.
|
||||
swapChain->present();
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
|
||||
@@ -1408,26 +1391,24 @@ void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
}
|
||||
|
||||
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands->insertEventMarker(string, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanDriver::pushGroupMarker(char const* string, uint32_t) {
|
||||
// Turns out all the markers are 0-terminated, so we can just pass it without len.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands->pushGroupMarker(string);
|
||||
#endif
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START(string);
|
||||
{
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::popGroupMarker(int) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands->popGroupMarker();
|
||||
#endif
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_END();
|
||||
{
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::startCapture(int) {}
|
||||
@@ -1458,9 +1439,6 @@ void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
|
||||
void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect, SamplerMagFilter filter) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("blit");
|
||||
|
||||
assert_invariant(mCurrentRenderPass.renderPass == VK_NULL_HANDLE);
|
||||
|
||||
// blit operation only support COLOR0 color buffer
|
||||
@@ -1500,16 +1478,12 @@ void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, V
|
||||
if (any(buffers & TargetBufferFlags::COLOR0)) {
|
||||
mBlitter.blitColor({ dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, int(0) });
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
const uint32_t instanceCount) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
VkCommandBuffer cmdbuffer = commands->cmdbuffer;
|
||||
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
|
||||
Handle<HwProgram> programHandle = pipelineState.program;
|
||||
@@ -1523,7 +1497,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
commands->acquire(prim.vertexBuffer);
|
||||
|
||||
// If this is a debug build, validate the current shader.
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
#if !defined(NDEBUG)
|
||||
if (program->bundle.vertex == VK_NULL_HANDLE || program->bundle.fragment == VK_NULL_HANDLE) {
|
||||
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
||||
}
|
||||
@@ -1639,7 +1613,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
// This fallback path is very flaky because the dummy texture might not have
|
||||
// matching characteristics. (e.g. if the missing texture is a 3D texture)
|
||||
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#ifndef NDEBUG
|
||||
utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'";
|
||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
|
||||
@@ -1713,7 +1687,6 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
const uint32_t firstInstId = 0;
|
||||
|
||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
// If any VkRenderPass or VkFramebuffer is unused for more than TIME_BEFORE_EVICTION frames, it
|
||||
// is evicted from the cache.
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS;
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = VK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -97,7 +97,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
|
||||
attachments[attachmentCount++] = config.depth;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
utils::slog.d << "Creating framebuffer " << config.width << "x" << config.height << " "
|
||||
<< "for render pass " << config.renderPass << ", "
|
||||
<< "samples = " << int(config.samples) << ", "
|
||||
@@ -312,7 +312,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
ASSERT_POSTCONDITION(!error, "Unable to create render pass.");
|
||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
utils::slog.d << "Created render pass " << renderPass << " with "
|
||||
<< "samples = " << int(config.samples) << ", "
|
||||
<< "depth = " << (hasDepth ? 1 : 0) << ", "
|
||||
@@ -338,9 +338,6 @@ void VulkanFboCache::reset() noexcept {
|
||||
// Frees up old framebuffers and render passes, then nulls out their key. Doesn't bother removing
|
||||
// the actual map entry since it is fairly small.
|
||||
void VulkanFboCache::gc() noexcept {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("fbocache::gc");
|
||||
|
||||
// If this is one of the first few frames, return early to avoid wrapping unsigned integers.
|
||||
if (++mCurrentTime <= TIME_BEFORE_EVICTION) {
|
||||
return;
|
||||
@@ -362,7 +359,6 @@ void VulkanFboCache::gc() noexcept {
|
||||
iter.value().handle = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -89,35 +89,30 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < specializationConstants.size(); i++) {
|
||||
uint32_t const offset = uint32_t(i) * 4;
|
||||
pEntries[i] = {
|
||||
.constantID = specializationConstants[i].id,
|
||||
.offset = offset,
|
||||
// Note that bools are 4-bytes in Vulkan
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
|
||||
.size = 4,
|
||||
};
|
||||
|
||||
using SpecConstant = Program::SpecializationConstant::Type;
|
||||
char const* addr = (char*)pData + offset;
|
||||
SpecConstant const& arg = specializationConstants[i].value;
|
||||
if (std::holds_alternative<bool>(arg)) {
|
||||
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
|
||||
} else if (std::holds_alternative<float>(arg)) {
|
||||
*((float*)addr) = std::get<float>(arg);
|
||||
} else {
|
||||
*((int32_t*)addr) = std::get<int32_t>(arg);
|
||||
}
|
||||
const uint32_t offset = uint32_t(i) * 4;
|
||||
std::visit([&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
pEntries[i] = {
|
||||
.constantID = specializationConstants[i].id,
|
||||
.offset = offset,
|
||||
// Turns out vulkan expects the size of bool to be 4 (verified through
|
||||
// validation layer). So all expected types are of 4 bytes.
|
||||
.size = 4,
|
||||
};
|
||||
T* const addr = (T*)((char*)pData + offset);
|
||||
*addr = arg;
|
||||
}, specializationConstants[i].value);
|
||||
}
|
||||
|
||||
bundle.specializationInfos = pInfo;
|
||||
}
|
||||
|
||||
// Make a copy of the binding map
|
||||
samplerGroupInfo = builder.getSamplerGroupInfo();
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
if constexpr (FILAMENT_VULKAN_VERBOSE) {
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex
|
||||
<< ", " << bundle.fragment << ")" << utils::io::endl;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept
|
||||
|
||||
@@ -96,6 +96,10 @@ struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
|
||||
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
|
||||
|
||||
inline void terminate() {
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
|
||||
|
||||
private:
|
||||
@@ -110,6 +114,9 @@ struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
buffer(allocator, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, elementSize * indexCount),
|
||||
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
|
||||
|
||||
void terminate() {
|
||||
buffer.terminate();
|
||||
}
|
||||
VulkanBuffer buffer;
|
||||
const VkIndexType indexType;
|
||||
};
|
||||
@@ -117,7 +124,9 @@ struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
|
||||
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage);
|
||||
|
||||
void terminate() {
|
||||
buffer.terminate();
|
||||
}
|
||||
VulkanBuffer buffer;
|
||||
const BufferObjectBinding bindingType;
|
||||
};
|
||||
|
||||
@@ -218,7 +218,7 @@ bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange&
|
||||
return false;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION | FVK_DEBUG_TEXTURE)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
#define CASE(VALUE) \
|
||||
case filament::backend::VulkanLayout::VALUE: { \
|
||||
out << #VALUE; \
|
||||
|
||||
@@ -166,7 +166,7 @@ bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
|
||||
}
|
||||
|
||||
bool VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) noexcept {
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
|
||||
|
||||
PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
|
||||
|
||||
@@ -481,10 +481,10 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
|
||||
PipelineCacheEntry cacheEntry = {};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
if constexpr (FILAMENT_VULKAN_VERBOSE) {
|
||||
utils::slog.d << "vkCreateGraphicsPipelines with shaders = ("
|
||||
<< shaderStages[0].module << ", " << shaderStages[1].module << ")" << utils::io::endl;
|
||||
#endif
|
||||
}
|
||||
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo,
|
||||
VKALLOC, &cacheEntry.handle);
|
||||
assert_invariant(error == VK_SUCCESS);
|
||||
@@ -678,10 +678,10 @@ void VulkanPipelineCache::terminate() noexcept {
|
||||
mDummyMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) {
|
||||
// The timestamp associated with a given cache entry represents "time" as a count of flush
|
||||
// events since the cache was constructed. If any cache entry was most recently used over
|
||||
// FVK_MAX_PIPELINE_AGE flush events in the past, then we can be sure that it is no longer
|
||||
// VK_MAX_PIPELINE_AGE flush events in the past, then we can be sure that it is no longer
|
||||
// being used by the GPU, and is therefore safe to destroy or reclaim.
|
||||
++mCurrentTime;
|
||||
|
||||
@@ -698,7 +698,7 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
using ConstDescIterator = decltype(mDescriptorSets)::const_iterator;
|
||||
for (ConstDescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end();) {
|
||||
const DescriptorCacheEntry& cacheEntry = iter.value();
|
||||
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
if (cacheEntry.lastUsed + VK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
auto& arenas = mPipelineLayouts[cacheEntry.pipelineLayout].descriptorSetArenas;
|
||||
for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) {
|
||||
arenas[i].push_back(cacheEntry.handles[i]);
|
||||
@@ -712,11 +712,11 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
}
|
||||
|
||||
// Evict any pipelines that have not been used in a while.
|
||||
// Any pipeline older than FVK_MAX_COMMAND_BUFFERS can be safely destroyed.
|
||||
// Any pipeline older than VK_MAX_COMMAND_BUFFERS can be safely destroyed.
|
||||
using ConstPipeIterator = decltype(mPipelines)::const_iterator;
|
||||
for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) {
|
||||
const PipelineCacheEntry& cacheEntry = iter.value();
|
||||
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
if (cacheEntry.lastUsed + VK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC);
|
||||
iter = mPipelines.erase(iter);
|
||||
} else {
|
||||
@@ -728,15 +728,15 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
using ConstLayoutIterator = decltype(mPipelineLayouts)::const_iterator;
|
||||
for (ConstLayoutIterator iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end();) {
|
||||
const PipelineLayoutCacheEntry& cacheEntry = iter.value();
|
||||
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
if (cacheEntry.lastUsed + VK_MAX_PIPELINE_AGE < mCurrentTime) {
|
||||
vkDestroyPipelineLayout(mDevice, iter->second.handle, VKALLOC);
|
||||
for (auto setLayout : iter->second.descriptorSetLayouts) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
|
||||
#ifndef NDEBUG
|
||||
PipelineLayoutKey key = iter.key();
|
||||
for (auto& pair : mDescriptorSets) {
|
||||
assert_invariant(pair.second.pipelineLayout != key);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC);
|
||||
}
|
||||
auto& arenas = iter->second.descriptorSetArenas;
|
||||
@@ -755,7 +755,7 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
// destroy the extinct pools, which implicitly frees their associated descriptor sets.
|
||||
bool canPurgeExtinctPools = true;
|
||||
for (auto& bundle : mExtinctDescriptorBundles) {
|
||||
if (bundle.lastUsed + FVK_MAX_PIPELINE_AGE >= mCurrentTime) {
|
||||
if (bundle.lastUsed + VK_MAX_PIPELINE_AGE >= mCurrentTime) {
|
||||
canPurgeExtinctPools = false;
|
||||
break;
|
||||
}
|
||||
@@ -799,7 +799,7 @@ VkDescriptorPool VulkanPipelineCache::createDescriptorPool(uint32_t size) const
|
||||
void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept {
|
||||
// Our current descriptor set strategy can cause the # of descriptor sets to explode in certain
|
||||
// situations, so it's interesting to report the number that get stuffed into the cache.
|
||||
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
|
||||
#ifndef NDEBUG
|
||||
utils::slog.d << "Destroying " << mDescriptorSets.size() << " bundles of descriptor sets."
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
@@ -808,7 +808,7 @@ void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept {
|
||||
|
||||
// Our current layout bundle strategy can cause the # of layout bundles to explode in certain
|
||||
// situations, so it's interesting to report the number that get stuffed into the cache.
|
||||
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
|
||||
#ifndef NDEBUG
|
||||
utils::slog.d << "Destroying " << mPipelineLayouts.size() << " pipeline layouts."
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
@@ -340,7 +340,7 @@ private:
|
||||
|
||||
// The timestamp associated with a given cache entry represents time as a count of flush
|
||||
// events since the cache was constructed. If any cache entry was most recently used over
|
||||
// FVK_MAX_PIPELINE_AGE flushes in the past, then we can be sure that it is no longer
|
||||
// VK_MAX_PIPELINE_AGE flushes in the past, then we can be sure that it is no longer
|
||||
// being used by the GPU, and is therefore safe to destroy or reclaim.
|
||||
using Timestamp = uint64_t;
|
||||
Timestamp mCurrentTime = 0;
|
||||
|
||||
@@ -167,7 +167,7 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
|
||||
VkImage stagingImage;
|
||||
vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_READ_PIXELS)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
utils::slog.d << "readPixels created image=" << stagingImage
|
||||
<< " to copy from image=" << srcTexture->getVkImage()
|
||||
<< " src-layout=" << srcTexture->getLayout(0, 0) << utils::io::endl;
|
||||
|
||||
@@ -101,6 +101,10 @@ public:
|
||||
template<typename D, typename B>
|
||||
inline void destruct(Handle<B> handle) noexcept {
|
||||
auto obj = handle_cast<D*>(handle);
|
||||
if constexpr (std::is_base_of_v<VulkanIndexBuffer, D>
|
||||
|| std::is_base_of_v<VulkanBufferObject, D>) {
|
||||
obj->terminate();
|
||||
}
|
||||
TRACK_DECREMENT();
|
||||
mHandleAllocatorImpl.deallocate(handle, obj);
|
||||
}
|
||||
|
||||
@@ -164,10 +164,6 @@ private:
|
||||
public:
|
||||
using const_iterator = FixedSizeArray::const_iterator;
|
||||
|
||||
inline ~FixedCapacityResourceSet() {
|
||||
clear();
|
||||
}
|
||||
|
||||
inline const_iterator begin() {
|
||||
if (mInd == 0) {
|
||||
return mArray.cend();
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS;
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = VK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -59,7 +59,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo,
|
||||
&allocInfo, &stage->buffer, &stage->memory, nullptr);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_ALLOCATION)
|
||||
#ifndef NDEBUG
|
||||
if (result != VK_SUCCESS) {
|
||||
utils::slog.e << "Allocation error: " << result << utils::io::endl;
|
||||
}
|
||||
@@ -111,7 +111,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
|
||||
VkImageAspectFlags const aspectFlags = getImageAspect(vkformat);
|
||||
VkCommandBuffer const cmdbuffer = mCommands->get().buffer();
|
||||
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
|
||||
|
||||
// We use VK_IMAGE_LAYOUT_GENERAL here because the spec says:
|
||||
// "Host access to image memory is only well-defined for linear images and for image
|
||||
@@ -129,9 +129,6 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
}
|
||||
|
||||
void VulkanStagePool::gc() noexcept {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("stagepool::gc");
|
||||
|
||||
// If this is one of the first few frames, return early to avoid wrapping unsigned integers.
|
||||
if (++mCurrentFrame <= TIME_BEFORE_EVICTION) {
|
||||
return;
|
||||
@@ -185,7 +182,6 @@ void VulkanStagePool::gc() noexcept {
|
||||
mUsedImages.insert(image);
|
||||
}
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanStagePool::terminate() noexcept {
|
||||
|
||||
@@ -79,7 +79,7 @@ void VulkanSwapChain::update() {
|
||||
|
||||
void VulkanSwapChain::present() {
|
||||
if (!mHeadless) {
|
||||
VkCommandBuffer const cmdbuf = mCommands->get().buffer();
|
||||
VkCommandBuffer const cmdbuf = mCommands->get().cmdbuffer;
|
||||
VkImageSubresourceRange const subresources{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
|
||||
@@ -106,7 +106,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#if VK_ENABLE_VALIDATION
|
||||
// Validate that the format is actually sampleable.
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, mVkFormat, &props);
|
||||
@@ -160,7 +160,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
||||
|
||||
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
|
||||
if (error || FVK_ENABLED_BOOL(FVK_DEBUG_TEXTURE)) {
|
||||
if (error || FILAMENT_VULKAN_VERBOSE) {
|
||||
utils::slog.d << "vkCreateImage: "
|
||||
<< "image = " << mTextureImage << ", "
|
||||
<< "result = " << error << ", "
|
||||
@@ -227,7 +227,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VkImageSubresourceRange range = { getImageAspect(), 0, levels, 0, layers };
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
VkCommandBuffer const cmdbuf = commands.cmdbuffer;
|
||||
commands.acquire(this);
|
||||
|
||||
transitionLayout(cmdbuf, range, ImgUtil::getDefaultLayout(imageInfo.usage));
|
||||
@@ -280,7 +280,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size);
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
VkCommandBuffer const cmdbuf = commands.cmdbuffer;
|
||||
commands.acquire(this);
|
||||
|
||||
VkBufferImageCopy copyRegion = {
|
||||
@@ -343,7 +343,7 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
||||
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size);
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
VkCommandBuffer const cmdbuf = commands.cmdbuffer;
|
||||
commands.acquire(this);
|
||||
|
||||
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
||||
@@ -417,7 +417,7 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres
|
||||
VulkanLayout newLayout) {
|
||||
VulkanLayout oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
utils::slog.i << "transition layout of " << mTextureImage << ",layer=" << range.baseArrayLayer
|
||||
<< ",level=" << range.baseMipLevel << " from=" << oldLayout << " to=" << newLayout
|
||||
<< " format=" << mVkFormat
|
||||
@@ -463,7 +463,7 @@ VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
|
||||
return mSubresourceLayouts.get(key);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
void VulkanTexture::print() const {
|
||||
const uint32_t firstLayer = 0;
|
||||
const uint32_t lastLayer = firstLayer + mPrimaryViewRange.layerCount;
|
||||
|
||||
@@ -84,7 +84,7 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
// For now this always returns either DEPTH or COLOR.
|
||||
VkImageAspectFlags getImageAspect() const;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
void print() const;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ constexpr uint32_t const INVALID_VK_INDEX = 0xFFFFFFFF;
|
||||
|
||||
typedef std::unordered_set<std::string_view> ExtensionSet;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
#if VK_ENABLE_VALIDATION
|
||||
// These strings need to be allocated outside a function stack
|
||||
const std::string_view DESIRED_LAYERS[] = {
|
||||
"VK_LAYER_KHRONOS_validation",
|
||||
#if FVK_ENABLED(FVK_DEBUG_DUMP_API)
|
||||
#if FILAMENT_VULKAN_DUMP_API
|
||||
"VK_LAYER_LUNARG_api_dump",
|
||||
#endif
|
||||
#if defined(ENABLE_RENDERDOC)
|
||||
@@ -75,7 +75,7 @@ FixedCapacityVector<const char*> getEnabledLayers() {
|
||||
}
|
||||
return enabledLayers;
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION)
|
||||
#endif
|
||||
|
||||
void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
// Print some driver or MoltenVK information if it is available.
|
||||
@@ -129,30 +129,30 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
<< minor << ")" << utils::io::endl;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
void printDepthFormats(VkPhysicalDevice device) {
|
||||
// For diagnostic purposes, print useful information about available depth formats.
|
||||
// Note that Vulkan is more constrained than OpenGL ES 3.1 in this area.
|
||||
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
|
||||
if constexpr (VK_ENABLE_VALIDATION && FILAMENT_VULKAN_VERBOSE) {
|
||||
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
|
||||
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||
utils::slog.i << "Sampleable depth formats: ";
|
||||
for (VkFormat format = (VkFormat) 1;;) {
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(device, format, &props);
|
||||
if ((props.optimalTilingFeatures & required) == required) {
|
||||
utils::slog.i << format << " ";
|
||||
utils::slog.i << "Sampleable depth formats: ";
|
||||
for (VkFormat format = (VkFormat) 1;;) {
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(device, format, &props);
|
||||
if ((props.optimalTilingFeatures & required) == required) {
|
||||
utils::slog.i << format << " ";
|
||||
}
|
||||
if (format == VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
|
||||
utils::slog.i << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
format = (VkFormat) (1 + (int) format);
|
||||
}
|
||||
if (format == VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
|
||||
utils::slog.i << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
format = (VkFormat) (1 + (int) format);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ExtensionSet getInstanceExtensions() {
|
||||
std::string_view const TARGET_EXTS[] = {
|
||||
std::array<std::string_view, 4> const TARGET_EXTS = {
|
||||
// Request all cross-platform extensions.
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
|
||||
@@ -160,10 +160,6 @@ ExtensionSet getInstanceExtensions() {
|
||||
// Request these if available.
|
||||
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
|
||||
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
ExtensionSet exts;
|
||||
FixedCapacityVector<VkExtensionProperties> const availableExts
|
||||
@@ -180,7 +176,7 @@ ExtensionSet getInstanceExtensions() {
|
||||
}
|
||||
|
||||
ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
std::string_view const TARGET_EXTS[] = {
|
||||
std::array<std::string_view, 5> const TARGET_EXTS = {
|
||||
VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
|
||||
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
|
||||
@@ -207,7 +203,7 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
VkInstanceCreateInfo instanceCreateInfo = {};
|
||||
bool validationFeaturesSupported = false;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
#if VK_ENABLE_VALIDATION
|
||||
auto const enabledLayers = getEnabledLayers();
|
||||
if (!enabledLayers.empty()) {
|
||||
// If layers are supported, Check if VK_EXT_validation_features is supported.
|
||||
@@ -229,17 +225,19 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
#else
|
||||
utils::slog.d << "Validation layer not available; did you install the Vulkan SDK?\n"
|
||||
<< "Please ensure that VK_LAYER_PATH is set correctly." << utils::io::endl;
|
||||
#endif // __ANDROID__
|
||||
|
||||
#endif
|
||||
}
|
||||
#endif // FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
#endif// VK_ENABLE_VALIDATION
|
||||
|
||||
// The Platform class can require 1 or 2 instance extensions, plus we'll request at most 5
|
||||
// instance extensions here in the common code. So that's a max of 7.
|
||||
static constexpr uint32_t MAX_INSTANCE_EXTENSION_COUNT = 8;
|
||||
static constexpr uint32_t MAX_INSTANCE_EXTENSION_COUNT = 7;
|
||||
const char* ppEnabledExtensions[MAX_INSTANCE_EXTENSION_COUNT];
|
||||
uint32_t enabledExtensionCount = 0;
|
||||
|
||||
#if VK_ENABLE_VALIDATION && defined(__ANDROID__)
|
||||
ppEnabledExtensions[enabledExtensionCount++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
|
||||
#endif
|
||||
if (validationFeaturesSupported) {
|
||||
ppEnabledExtensions[enabledExtensionCount++] = VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME;
|
||||
}
|
||||
@@ -253,7 +251,7 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
VkApplicationInfo appInfo = {};
|
||||
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
appInfo.apiVersion
|
||||
= VK_MAKE_API_VERSION(0, FVK_REQUIRED_VERSION_MAJOR, FVK_REQUIRED_VERSION_MINOR, 0);
|
||||
= VK_MAKE_API_VERSION(0, VK_REQUIRED_VERSION_MAJOR, VK_REQUIRED_VERSION_MINOR, 0);
|
||||
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
instanceCreateInfo.pApplicationInfo = &appInfo;
|
||||
instanceCreateInfo.enabledExtensionCount = enabledExtensionCount;
|
||||
@@ -275,8 +273,12 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
}
|
||||
|
||||
VkResult result = vkCreateInstance(&instanceCreateInfo, VKALLOC, &instance);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan instance. Result=%d",
|
||||
result);
|
||||
#ifndef NDEBUG
|
||||
if (result != VK_SUCCESS) {
|
||||
utils::slog.e << "Unable to create instance: " << result << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan instance.");
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -440,10 +442,10 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
int const minor = VK_VERSION_MINOR(targetDeviceProperties.apiVersion);
|
||||
|
||||
// Does the device support the required Vulkan level?
|
||||
if (major < FVK_REQUIRED_VERSION_MAJOR) {
|
||||
if (major < VK_REQUIRED_VERSION_MAJOR) {
|
||||
continue;
|
||||
}
|
||||
if (major == FVK_REQUIRED_VERSION_MAJOR && minor < FVK_REQUIRED_VERSION_MINOR) {
|
||||
if (major == VK_REQUIRED_VERSION_MAJOR && minor < VK_REQUIRED_VERSION_MINOR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -510,7 +512,7 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
|
||||
VkFormat findSupportedFormat(VkPhysicalDevice device) {
|
||||
VkFormatFeatureFlags const features = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
VkFormat const formats[] = {VK_FORMAT_D32_SFLOAT, VK_FORMAT_X8_D24_UNORM_PACK32};
|
||||
std::array<VkFormat, 2> const formats = {VK_FORMAT_D32_SFLOAT, VK_FORMAT_X8_D24_UNORM_PACK32};
|
||||
for (VkFormat format: formats) {
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(device, format, &props);
|
||||
@@ -664,9 +666,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
// just yet, since that would require a corollary change to the "aspect" flags for the VkImage.
|
||||
context.mDepthFormat = findSupportedFormat(mImpl->mPhysicalDevice);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
printDepthFormats(mImpl->mPhysicalDevice);
|
||||
#endif
|
||||
|
||||
// Keep a copy of context for swapchains.
|
||||
mImpl->mContext = context;
|
||||
|
||||
@@ -211,5 +211,66 @@ int runTests() {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
void getPixelInfo(PixelDataFormat format, PixelDataType type, size_t& outComponents, int& outBpp) {
|
||||
assert_invariant(type != PixelDataType::COMPRESSED);
|
||||
switch (format) {
|
||||
case PixelDataFormat::UNUSED:
|
||||
case PixelDataFormat::R:
|
||||
case PixelDataFormat::R_INTEGER:
|
||||
case PixelDataFormat::DEPTH_COMPONENT:
|
||||
case PixelDataFormat::ALPHA:
|
||||
outComponents = 1;
|
||||
break;
|
||||
case PixelDataFormat::RG:
|
||||
case PixelDataFormat::RG_INTEGER:
|
||||
case PixelDataFormat::DEPTH_STENCIL:
|
||||
outComponents = 2;
|
||||
break;
|
||||
case PixelDataFormat::RGB:
|
||||
case PixelDataFormat::RGB_INTEGER:
|
||||
outComponents = 3;
|
||||
break;
|
||||
case PixelDataFormat::RGBA:
|
||||
case PixelDataFormat::RGBA_INTEGER:
|
||||
outComponents = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
outBpp = outComponents;
|
||||
switch (type) {
|
||||
case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings
|
||||
case PixelDataType::UBYTE:
|
||||
case PixelDataType::BYTE:
|
||||
// nothing to do
|
||||
break;
|
||||
case PixelDataType::USHORT:
|
||||
case PixelDataType::SHORT:
|
||||
case PixelDataType::HALF:
|
||||
outBpp *= 2;
|
||||
break;
|
||||
case PixelDataType::UINT:
|
||||
case PixelDataType::INT:
|
||||
case PixelDataType::FLOAT:
|
||||
outBpp *= 4;
|
||||
break;
|
||||
case PixelDataType::UINT_10F_11F_11F_REV:
|
||||
// Special case, format must be RGB and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::UINT_2_10_10_10_REV:
|
||||
// Special case, format must be RGBA and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGBA);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::USHORT_565:
|
||||
// Special case, format must be RGB and uses 2 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace test
|
||||
|
||||
|
||||
@@ -75,6 +75,12 @@ private:
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> uniform;
|
||||
};
|
||||
|
||||
|
||||
// Utilities
|
||||
|
||||
void getPixelInfo(filament::backend::PixelDataFormat format, filament::backend::PixelDataType type,
|
||||
size_t& outComponents, int& outBpp);
|
||||
|
||||
} // namespace test
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 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_BACKENDTESTUTILS_H
|
||||
#define TNT_BACKENDTESTUTILS_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
inline void getPixelInfo(PixelDataFormat format, PixelDataType type, size_t& outComponents, int& outBpp) {
|
||||
assert_invariant(type != PixelDataType::COMPRESSED);
|
||||
switch (format) {
|
||||
case PixelDataFormat::UNUSED:
|
||||
case PixelDataFormat::R:
|
||||
case PixelDataFormat::R_INTEGER:
|
||||
case PixelDataFormat::DEPTH_COMPONENT:
|
||||
case PixelDataFormat::ALPHA:
|
||||
outComponents = 1;
|
||||
break;
|
||||
case PixelDataFormat::RG:
|
||||
case PixelDataFormat::RG_INTEGER:
|
||||
case PixelDataFormat::DEPTH_STENCIL:
|
||||
outComponents = 2;
|
||||
break;
|
||||
case PixelDataFormat::RGB:
|
||||
case PixelDataFormat::RGB_INTEGER:
|
||||
outComponents = 3;
|
||||
break;
|
||||
case PixelDataFormat::RGBA:
|
||||
case PixelDataFormat::RGBA_INTEGER:
|
||||
outComponents = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
outBpp = outComponents;
|
||||
switch (type) {
|
||||
case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings
|
||||
case PixelDataType::UBYTE:
|
||||
case PixelDataType::BYTE:
|
||||
// nothing to do
|
||||
break;
|
||||
case PixelDataType::USHORT:
|
||||
case PixelDataType::SHORT:
|
||||
case PixelDataType::HALF:
|
||||
outBpp *= 2;
|
||||
break;
|
||||
case PixelDataType::UINT:
|
||||
case PixelDataType::INT:
|
||||
case PixelDataType::FLOAT:
|
||||
outBpp *= 4;
|
||||
break;
|
||||
case PixelDataType::UINT_10F_11F_11F_REV:
|
||||
// Special case, format must be RGB and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::UINT_2_10_10_10_REV:
|
||||
// Special case, format must be RGBA and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGBA);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::USHORT_565:
|
||||
// Special case, format must be RGB and uses 2 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ComponentType>
|
||||
static void fillCheckerboard(void* buffer, size_t size, size_t stride, size_t components,
|
||||
ComponentType value) {
|
||||
ComponentType* row = (ComponentType*)buffer;
|
||||
int p = 0;
|
||||
for (int r = 0; r < size; r++) {
|
||||
ComponentType* pixel = row;
|
||||
for (int col = 0; col < size; col++) {
|
||||
// Generate a checkerboard pattern.
|
||||
if ((p & 0x0010) ^ ((p / size) & 0x0010)) {
|
||||
// Turn on the first component (red).
|
||||
pixel[0] = value;
|
||||
}
|
||||
pixel += components;
|
||||
p++;
|
||||
}
|
||||
row += stride * components;
|
||||
}
|
||||
}
|
||||
|
||||
static PixelBufferDescriptor checkerboardPixelBuffer(PixelDataFormat format, PixelDataType type,
|
||||
size_t size, size_t bufferPadding = 0) {
|
||||
size_t components; int bpp;
|
||||
getPixelInfo(format, type, components, bpp);
|
||||
|
||||
size_t bufferSize = size + bufferPadding * 2;
|
||||
uint8_t* buffer = (uint8_t*) calloc(1, bufferSize * bufferSize * bpp);
|
||||
|
||||
uint8_t* ptr = buffer + (bufferSize * bufferPadding * bpp) + (bufferPadding * bpp);
|
||||
|
||||
switch (type) {
|
||||
case PixelDataType::BYTE:
|
||||
fillCheckerboard<int8_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::UBYTE:
|
||||
fillCheckerboard<uint8_t>(ptr, size, bufferSize, components, 0xFF);
|
||||
break;
|
||||
|
||||
case PixelDataType::SHORT:
|
||||
fillCheckerboard<int16_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::USHORT:
|
||||
fillCheckerboard<uint16_t>(ptr, size, bufferSize, components, 1u);
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, components, 1u);
|
||||
break;
|
||||
|
||||
case PixelDataType::INT:
|
||||
fillCheckerboard<int32_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::FLOAT:
|
||||
fillCheckerboard<float>(ptr, size, bufferSize, components, 1.0f);
|
||||
break;
|
||||
|
||||
case PixelDataType::HALF:
|
||||
fillCheckerboard<math::half>(ptr, size, bufferSize, components, math::half(1.0f));
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT_2_10_10_10_REV:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, 1, 0xC00003FF /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::USHORT_565:
|
||||
fillCheckerboard<uint16_t>(ptr, size, bufferSize, 1, 0xF800 /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT_10F_11F_11F_REV:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, 1, 0x000003C0 /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::COMPRESSED:
|
||||
break;
|
||||
}
|
||||
|
||||
PixelBufferDescriptor descriptor(buffer, bufferSize * bufferSize * bpp, format, type,
|
||||
1, bufferPadding, bufferPadding, bufferSize, [](void* buffer, size_t size, void* user) {
|
||||
free(buffer);
|
||||
}, nullptr);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#endif // TNT_BACKENDTESTUTILS_H
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "BackendTestUtils.h"
|
||||
|
||||
#include "private/filament/SamplerInterfaceBlock.h"
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
@@ -114,7 +113,91 @@ namespace test {
|
||||
|
||||
template<typename componentType> inline componentType getMaxValue();
|
||||
|
||||
template<typename ComponentType>
|
||||
static void fillCheckerboard(void* buffer, size_t size, size_t stride, size_t components,
|
||||
ComponentType value) {
|
||||
ComponentType* row = (ComponentType*)buffer;
|
||||
int p = 0;
|
||||
for (int r = 0; r < size; r++) {
|
||||
ComponentType* pixel = row;
|
||||
for (int col = 0; col < size; col++) {
|
||||
// Generate a checkerboard pattern.
|
||||
if ((p & 0x0010) ^ ((p / size) & 0x0010)) {
|
||||
// Turn on the first component (red).
|
||||
pixel[0] = value;
|
||||
}
|
||||
pixel += components;
|
||||
p++;
|
||||
}
|
||||
row += stride * components;
|
||||
}
|
||||
}
|
||||
|
||||
static PixelBufferDescriptor checkerboardPixelBuffer(PixelDataFormat format, PixelDataType type,
|
||||
size_t size, size_t bufferPadding = 0) {
|
||||
size_t components; int bpp;
|
||||
getPixelInfo(format, type, components, bpp);
|
||||
|
||||
size_t bufferSize = size + bufferPadding * 2;
|
||||
uint8_t* buffer = (uint8_t*) calloc(1, bufferSize * bufferSize * bpp);
|
||||
|
||||
uint8_t* ptr = buffer + (bufferSize * bufferPadding * bpp) + (bufferPadding * bpp);
|
||||
|
||||
switch (type) {
|
||||
case PixelDataType::BYTE:
|
||||
fillCheckerboard<int8_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::UBYTE:
|
||||
fillCheckerboard<uint8_t>(ptr, size, bufferSize, components, 0xFF);
|
||||
break;
|
||||
|
||||
case PixelDataType::SHORT:
|
||||
fillCheckerboard<int16_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::USHORT:
|
||||
fillCheckerboard<uint16_t>(ptr, size, bufferSize, components, 1u);
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, components, 1u);
|
||||
break;
|
||||
|
||||
case PixelDataType::INT:
|
||||
fillCheckerboard<int32_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::FLOAT:
|
||||
fillCheckerboard<float>(ptr, size, bufferSize, components, 1.0f);
|
||||
break;
|
||||
|
||||
case PixelDataType::HALF:
|
||||
fillCheckerboard<math::half>(ptr, size, bufferSize, components, math::half(1.0f));
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT_2_10_10_10_REV:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, 1, 0xC00003FF /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::USHORT_565:
|
||||
fillCheckerboard<uint16_t>(ptr, size, bufferSize, 1, 0xF800 /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT_10F_11F_11F_REV:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, 1, 0x000003C0 /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::COMPRESSED:
|
||||
break;
|
||||
}
|
||||
|
||||
PixelBufferDescriptor descriptor(buffer, bufferSize * bufferSize * bpp, format, type,
|
||||
1, bufferPadding, bufferPadding, bufferSize, [](void* buffer, size_t size, void* user) {
|
||||
free(buffer);
|
||||
}, nullptr);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
inline std::string stringReplace(const std::string& find, const std::string& replace,
|
||||
std::string source) {
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
/*
|
||||
* 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 "BackendTest.h"
|
||||
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "BackendTestUtils.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
namespace {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Shaders
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::string vertex (R"(#version 450 core
|
||||
|
||||
layout(location = 0) in vec4 mesh_position;
|
||||
layout(location = 0) out vec2 uv;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
|
||||
uv = (mesh_position.xy * 0.5 + 0.5);
|
||||
}
|
||||
)");
|
||||
|
||||
std::string fragment (R"(#version 450 core
|
||||
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
layout(location = 0) in vec2 uv;
|
||||
|
||||
layout(location = 0, set = 1) uniform sampler2D backend_test_sib_tex;
|
||||
|
||||
void main() {
|
||||
fragColor = textureLod(backend_test_sib_tex, uv, 1);
|
||||
}
|
||||
)");
|
||||
|
||||
std::string whiteFragment (R"(#version 450 core
|
||||
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
layout(location = 0) in vec2 uv;
|
||||
|
||||
layout(location = 0, set = 1) uniform sampler2D backend_test_sib_tex;
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(1.0);
|
||||
}
|
||||
)");
|
||||
|
||||
}
|
||||
|
||||
namespace test {
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
auto& api = getDriverApi();
|
||||
api.startCapture(0);
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
{
|
||||
// Create a SwapChain and make it current.
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program that draws only white.
|
||||
Handle<HwProgram> whiteProgram;
|
||||
{
|
||||
ShaderGenerator shaderGen(vertex, whiteFragment, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0};
|
||||
p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1);
|
||||
whiteProgram = api.createProgram(std::move(p));
|
||||
}
|
||||
|
||||
// Create a program that samples a texture.
|
||||
Handle<HwProgram> textureProgram;
|
||||
{
|
||||
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
|
||||
.name("backend_test_sib")
|
||||
.stageFlags(backend::ShaderStageFlags::FRAGMENT)
|
||||
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
|
||||
.build();
|
||||
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0};
|
||||
p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1);
|
||||
textureProgram = api.createProgram(std::move(p));
|
||||
}
|
||||
|
||||
// Create a texture that has 4 mip levels. Each level is a different color.
|
||||
// Level 0: 128x128 (red)
|
||||
// Level 1: 64x64 (green)
|
||||
// Level 2: 32x32 (blue)
|
||||
// Level 3: 16x16 (yellow)
|
||||
const size_t kTextureSize = 128;
|
||||
const size_t kMipLevels = 4;
|
||||
Handle<HwTexture> texture = api.createTexture(SamplerType::SAMPLER_2D, kMipLevels,
|
||||
TextureFormat::RGBA8, 1, kTextureSize, kTextureSize, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::UPLOADABLE);
|
||||
|
||||
// Create image data.
|
||||
auto pixelFormat = PixelDataFormat::RGBA;
|
||||
auto pixelType = PixelDataType::UBYTE;
|
||||
size_t components; int bpp;
|
||||
getPixelInfo(pixelFormat, pixelType, components, bpp);
|
||||
uint32_t colors[] = {
|
||||
0xFF0000FF, /* red */
|
||||
0xFF00FF00, /* green */
|
||||
0xFFFF0000, /* blue */
|
||||
0xFF00FFFF, /* yellow */
|
||||
};
|
||||
for (int l = 0; l < kMipLevels; l++) {
|
||||
size_t mipSize = kTextureSize >> l;
|
||||
auto* buffer = (uint8_t*)calloc(1, mipSize * mipSize * bpp);
|
||||
fillCheckerboard<uint32_t>(buffer, mipSize, mipSize, 1, colors[l]);
|
||||
PixelBufferDescriptor descriptor(
|
||||
buffer, mipSize * mipSize * bpp, pixelFormat, pixelType, 1, 0, 0,
|
||||
mipSize, [](void* buffer, size_t size, void* user) { free(buffer); },
|
||||
nullptr);
|
||||
api.update3DImage(
|
||||
texture, l, 0, 0, 0, mipSize, mipSize, 1, std::move(descriptor));
|
||||
}
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
api.beginFrame(0, 0);
|
||||
|
||||
// We set the base mip to 1, and the max mip to 3
|
||||
// Level 0: 128x128 (red)
|
||||
// Level 1: 64x64 (green) <-- base
|
||||
// Level 2: 32x32 (blue) <--- white triangle rendered
|
||||
// Level 3: 16x16 (yellow) <-- max
|
||||
api.setMinMaxLevels(texture, 1, 3);
|
||||
|
||||
// Render a white triangle into level 2.
|
||||
// We specify mip level 2, because minMaxLevels has no effect when rendering into a texture.
|
||||
Handle<HwRenderTarget> renderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, 32, 32, 1,
|
||||
{texture, 2 /* level */, 0 /* layer */}, {}, {});
|
||||
{
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
PipelineState ps = {};
|
||||
ps.program = whiteProgram;
|
||||
ps.rasterState.colorWrite = true;
|
||||
ps.rasterState.depthWrite = false;
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
backend::Handle<HwRenderTarget> defaultRenderTarget = api.createDefaultRenderTarget(0);
|
||||
|
||||
RenderPassParams params = {};
|
||||
fullViewport(params);
|
||||
params.flags.clear = TargetBufferFlags::COLOR;
|
||||
params.clearColor = {0.f, 0.f, 0.5f, 1.f};
|
||||
params.flags.discardStart = TargetBufferFlags::ALL;
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
PipelineState state;
|
||||
state.scissor = params.viewport;
|
||||
state.program = textureProgram;
|
||||
state.rasterState.colorWrite = true;
|
||||
state.rasterState.depthWrite = false;
|
||||
state.rasterState.depthFunc = SamplerCompareFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
|
||||
SamplerGroup samplers(1);
|
||||
SamplerParams samplerParams {};
|
||||
samplerParams.filterMag = SamplerMagFilter::NEAREST;
|
||||
samplerParams.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
|
||||
samplers.setSampler(0, { texture, samplerParams });
|
||||
backend::Handle<HwSamplerGroup> samplerGroup = api.createSamplerGroup(1);
|
||||
api.updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(api));
|
||||
api.bindSamplers(0, samplerGroup);
|
||||
|
||||
// Render a triangle to the screen, sampling from mip level 1.
|
||||
// Because the min level is 1, the result color should be the white triangle drawn in the
|
||||
// previous pass.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
// Adjust the base mip to 2.
|
||||
// Note that this is done without another call to updateSamplerGroup.
|
||||
api.setMinMaxLevels(texture, 2, 3);
|
||||
|
||||
// Render a second, smaller, triangle, again sampling from mip level 1.
|
||||
// This triangle should be yellow striped.
|
||||
static filament::math::float2 vertices[3] = {
|
||||
{ -0.5, -0.5 },
|
||||
{ 0.5, -0.5 },
|
||||
{ -0.5, 0.5 }
|
||||
};
|
||||
triangle.updateVertices(vertices);
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
// Cleanup.
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(renderTarget);
|
||||
api.destroyTexture(texture);
|
||||
api.destroyProgram(whiteProgram);
|
||||
api.destroyProgram(textureProgram);
|
||||
}
|
||||
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "BackendTestUtils.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <utils/EntityInstance.h>
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
#include <math/quat.h>
|
||||
|
||||
namespace utils {
|
||||
class Entity;
|
||||
@@ -245,7 +244,6 @@ public:
|
||||
* shadows that are too far and wouldn't contribute to the scene much, improving
|
||||
* performance and quality. This value is always positive.
|
||||
* Use 0.0f to use the camera far distance.
|
||||
* This only affect directional lights.
|
||||
*/
|
||||
float shadowFar = 0.0f;
|
||||
|
||||
@@ -362,13 +360,6 @@ public:
|
||||
* enabled. (2cm by default).
|
||||
*/
|
||||
float shadowBulbRadius = 0.02f;
|
||||
|
||||
/**
|
||||
* Transforms the shadow direction. Must be a unit quaternion.
|
||||
* The default is identity.
|
||||
* Ignored if the light type isn't directional. For artistic use. Use with caution.
|
||||
*/
|
||||
math::quatf transform{ 1.0f };
|
||||
};
|
||||
|
||||
struct ShadowCascades {
|
||||
@@ -680,7 +671,7 @@ public:
|
||||
* @return true is this light is a type of directional light
|
||||
*/
|
||||
inline bool isDirectional(Instance i) const noexcept {
|
||||
Type const type = getType(i);
|
||||
Type type = getType(i);
|
||||
return type == Type::DIRECTIONAL || type == Type::SUN;
|
||||
}
|
||||
|
||||
@@ -701,7 +692,7 @@ public:
|
||||
* @return true is this light is a type of spot light
|
||||
*/
|
||||
inline bool isSpotLight(Instance i) const noexcept {
|
||||
Type const type = getType(i);
|
||||
Type type = getType(i);
|
||||
return type == Type::SPOT || type == Type::FOCUSED_SPOT;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,17 +141,6 @@ struct BloomOptions {
|
||||
bool enabled = false; //!< enable or disable bloom
|
||||
float highlight = 1000.0f; //!< limit highlights to this value before bloom [10, +inf]
|
||||
|
||||
/**
|
||||
* Bloom quality level.
|
||||
* LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.
|
||||
* MEDIUM: Good balance between quality and performance.
|
||||
* HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||
* This mode can be significantly slower on mobile, especially at high resolution.
|
||||
* This mode greatly improves the anamorphic bloom.
|
||||
*/
|
||||
QualityLevel quality = QualityLevel::LOW;
|
||||
|
||||
bool lensFlare = false; //!< enable screen-space lens flare
|
||||
bool starburst = true; //!< enable starburst effect on lens flare
|
||||
float chromaticAberration = 0.005f; //!< amount of chromatic aberration
|
||||
|
||||
@@ -285,7 +285,7 @@ void PerViewUniforms::prepareDirectionalLight(FEngine& engine,
|
||||
float exposure,
|
||||
float3 const& sceneSpaceDirection,
|
||||
PerViewUniforms::LightManagerInstance directionalLight) noexcept {
|
||||
FLightManager const& lcm = engine.getLightManager();
|
||||
FLightManager& lcm = engine.getLightManager();
|
||||
auto& s = mUniforms.edit();
|
||||
|
||||
const float3 l = -sceneSpaceDirection; // guaranteed normalized
|
||||
@@ -302,11 +302,11 @@ void PerViewUniforms::prepareDirectionalLight(FEngine& engine,
|
||||
// The last parameter must be < 0.0f for regular directional lights
|
||||
float4 sun{ 0.0f, 0.0f, 0.0f, -1.0f };
|
||||
if (UTILS_UNLIKELY(isSun && colorIntensity.w > 0.0f)) {
|
||||
// Currently we have only a single directional light, so it's probably likely that it's
|
||||
// currently we have only a single directional light, so it's probably likely that it's
|
||||
// also the Sun. However, conceptually, most directional lights won't be sun lights.
|
||||
float const radius = lcm.getSunAngularRadius(directionalLight);
|
||||
float const haloSize = lcm.getSunHaloSize(directionalLight);
|
||||
float const haloFalloff = lcm.getSunHaloFalloff(directionalLight);
|
||||
float radius = lcm.getSunAngularRadius(directionalLight);
|
||||
float haloSize = lcm.getSunHaloSize(directionalLight);
|
||||
float haloFalloff = lcm.getSunHaloFalloff(directionalLight);
|
||||
sun.x = std::cos(radius);
|
||||
sun.y = std::sin(radius);
|
||||
sun.z = 1.0f / (std::cos(radius * haloSize) - sun.x);
|
||||
|
||||
+128
-142
@@ -212,8 +212,6 @@ static const PostProcessManager::MaterialInfo sMaterialList[] = {
|
||||
{ "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) },
|
||||
{ "blitLow", MATERIAL(BLITLOW) },
|
||||
{ "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) },
|
||||
{ "bloomDownsample2x", MATERIAL(BLOOMDOWNSAMPLE2X) },
|
||||
{ "bloomDownsample9", MATERIAL(BLOOMDOWNSAMPLE9) },
|
||||
{ "bloomUpsample", MATERIAL(BLOOMUPSAMPLE) },
|
||||
{ "colorGrading", MATERIAL(COLORGRADING) },
|
||||
{ "colorGradingAsSubpass", MATERIAL(COLORGRADINGASSUBPASS) },
|
||||
@@ -452,6 +450,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
for (size_t level = 0; level < levelCount - 1; level++) {
|
||||
auto out = resources.getRenderPassInfo(level);
|
||||
driver.setMinMaxLevels(in, level, level);
|
||||
mi->setParameter("level", uint32_t(level));
|
||||
commitAndRender(out, material, driver);
|
||||
}
|
||||
driver.setMinMaxLevels(in, 0, levelCount - 1);
|
||||
@@ -1597,7 +1596,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
auto const& out = resources.getRenderPassInfo(data.rp[level]);
|
||||
driver.setMinMaxLevels(inOutColor, level, level);
|
||||
driver.setMinMaxLevels(inOutCoc, level, level);
|
||||
mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres?
|
||||
mi->setParameter("mip", uint32_t(level));
|
||||
mi->setParameter("weightScale", 0.5f / float(1u<<level)); // FIXME: halfres?
|
||||
mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h });
|
||||
mi->commit(driver);
|
||||
render(out, pipeline, driver);
|
||||
@@ -1870,53 +1870,15 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
return bloomPass(fg, input, outFormat, inoutBloomOptions, scale);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::downscalePass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
bool threshold, float highlight, bool fireflies) noexcept {
|
||||
struct DownsampleData {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
auto& downsamplePass = fg.addPass<DownsampleData>("Downsample",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture("Downsample-output", outDesc);
|
||||
builder.declareRenderPass(data.output);
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
auto const& material = getPostProcessMaterial("bloomDownsample2x");
|
||||
auto* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("source", resources.getTexture(data.input), {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
mi->setParameter("level", 0);
|
||||
mi->setParameter("threshold", threshold ? 1.0f : 0.0f);
|
||||
mi->setParameter("fireflies", fireflies ? 1.0f : 0.0f);
|
||||
mi->setParameter("invHighlight", std::isinf(highlight) ? 0.0f : 1.0f / highlight);
|
||||
commitAndRender(resources.getRenderPassInfo(), material, driver);
|
||||
});
|
||||
return downsamplePass->output;
|
||||
}
|
||||
|
||||
PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, TextureFormat outFormat,
|
||||
BloomOptions& inoutBloomOptions, float2 scale) noexcept {
|
||||
|
||||
// Figure out a good size for the bloom buffer. We must use a fixed bloom buffer size so
|
||||
// that the size/strength of the bloom doesn't vary much with the resolution, otherwise
|
||||
// dynamic resolution would affect the bloom effect too much.
|
||||
auto desc = fg.getDescriptor(input);
|
||||
// Figure out a good size for the bloom buffer.
|
||||
auto const& desc = fg.getDescriptor(input);
|
||||
|
||||
// width and height after dynamic resolution upscaling
|
||||
const float aspect = (float(desc.width) * scale.y) / (float(desc.height) * scale.x);
|
||||
|
||||
// FIXME: don't allow inoutBloomOptions.resolution to be larger than input's resolution
|
||||
// (avoid upscale) but how does this affect dynamic resolution
|
||||
// FIXME: check what happens on WebGL and intel's processors
|
||||
|
||||
// compute the desired bloom buffer size
|
||||
float bloomHeight = float(inoutBloomOptions.resolution);
|
||||
float bloomWidth = bloomHeight * aspect;
|
||||
@@ -1931,73 +1893,38 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
bloomHeight *= inoutBloomOptions.anamorphism;
|
||||
}
|
||||
|
||||
// convert back to integer width/height
|
||||
const uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
const uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
|
||||
// we might need to adjust the max # of levels
|
||||
const uint32_t major = uint32_t(std::max(bloomWidth, bloomHeight));
|
||||
const uint8_t maxLevels = FTexture::maxLevelCount(major);
|
||||
inoutBloomOptions.levels = std::min(inoutBloomOptions.levels, maxLevels);
|
||||
inoutBloomOptions.levels = std::min(inoutBloomOptions.levels, kMaxBloomLevels);
|
||||
|
||||
if (inoutBloomOptions.quality == QualityLevel::LOW) {
|
||||
// In low quality mode, we adjust the bloom buffer size so that both dimensions
|
||||
// have enough exact mip levels. This can slightly affect the aspect ratio causing
|
||||
// some artifacts:
|
||||
// - add some anamorphism (experimentally not visible)
|
||||
// - visible bloom size changes with dynamic resolution in non-homogenous mode
|
||||
// This allows us to use the 9 sample downsampling filter (instead of 13)
|
||||
// for at least 4 levels.
|
||||
uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
width &= ~((1 << 4) - 1); // at least 4 levels
|
||||
height &= ~((1 << 4) - 1);
|
||||
bloomWidth = float(width);
|
||||
bloomHeight = float(height);
|
||||
if (2 * width < desc.width || 2 * height < desc.height) {
|
||||
// if we're scaling down by more than 2x, prescale the image with a blit to improve
|
||||
// performance. This is important on mobile/tilers.
|
||||
input = opaqueBlit(fg, input, { 0, 0, desc.width, desc.height }, {
|
||||
.width = std::max(1u, desc.width / 2),
|
||||
.height = std::max(1u, desc.height / 2),
|
||||
.format = outFormat
|
||||
});
|
||||
}
|
||||
|
||||
bool threshold = inoutBloomOptions.threshold;
|
||||
|
||||
while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) {
|
||||
if (inoutBloomOptions.quality == QualityLevel::LOW ||
|
||||
inoutBloomOptions.quality == QualityLevel::MEDIUM) {
|
||||
input = downscalePass(fg, input, {
|
||||
.width = (desc.width = std::max(1u, desc.width / 2)),
|
||||
.height = (desc.height = std::max(1u, desc.height / 2)),
|
||||
.format = outFormat
|
||||
},
|
||||
threshold, inoutBloomOptions.highlight, threshold);
|
||||
threshold = false; // we do the thresholding only once during down sampling
|
||||
} else if (inoutBloomOptions.quality == QualityLevel::HIGH ||
|
||||
inoutBloomOptions.quality == QualityLevel::ULTRA) {
|
||||
// In high quality mode, we increase the size of the bloom buffer such that the
|
||||
// first scaling is less than 2x, and we increase the number of levels accordingly.
|
||||
if (bloomWidth * 2.0f > 2048.0f || bloomHeight * 2.0f > 2048.0f) {
|
||||
// but we can't scale above the h/w guaranteed minspec
|
||||
break;
|
||||
}
|
||||
bloomWidth *= 2.0f;
|
||||
bloomHeight *= 2.0f;
|
||||
inoutBloomOptions.levels++;
|
||||
}
|
||||
}
|
||||
|
||||
// convert back to integer width/height
|
||||
uint32_t const width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t const height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
|
||||
input = downscalePass(fg, input,
|
||||
{ .width = width, .height = height, .format = outFormat },
|
||||
threshold, inoutBloomOptions.highlight, threshold);
|
||||
|
||||
struct BloomPassData {
|
||||
FrameGraphId<FrameGraphTexture> in;
|
||||
FrameGraphId<FrameGraphTexture> out;
|
||||
FrameGraphId<FrameGraphTexture> stage;
|
||||
uint32_t outRT[kMaxBloomLevels];
|
||||
uint32_t stageRT[kMaxBloomLevels];
|
||||
};
|
||||
|
||||
// Creating a mip-chain poses a "feedback" loop problem on some GPU. We will disable
|
||||
// Bloom on these.
|
||||
// See: https://github.com/google/filament/issues/2338
|
||||
|
||||
// downsample phase
|
||||
auto& bloomDownsamplePass = fg.addPass<BloomPassData>("Bloom Downsample",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.in = builder.sample(input);
|
||||
data.out = builder.createTexture("Bloom Out Texture", {
|
||||
.width = width,
|
||||
.height = height,
|
||||
@@ -2006,107 +1933,165 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
});
|
||||
data.out = builder.sample(data.out);
|
||||
|
||||
data.stage = builder.createTexture("Bloom Stage Texture", {
|
||||
.width = width,
|
||||
.height = height,
|
||||
.levels = inoutBloomOptions.levels,
|
||||
.format = outFormat
|
||||
});
|
||||
data.stage = builder.sample(data.stage);
|
||||
|
||||
for (size_t i = 0; i < inoutBloomOptions.levels; i++) {
|
||||
auto out = builder.createSubresource(data.out, "Bloom Out Texture mip",
|
||||
{ .level = uint8_t(i) });
|
||||
if (i == 0) {
|
||||
// this causes the last blit above to render into this mip
|
||||
fg.forwardResource(out, input);
|
||||
}
|
||||
auto stage = builder.createSubresource(data.stage,
|
||||
"Bloom Stage Texture mip", { .level = uint8_t(i) });
|
||||
builder.declareRenderPass(out, &data.outRT[i]);
|
||||
builder.declareRenderPass(stage, &data.stageRT[i]);
|
||||
}
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
|
||||
// TODO: if downsampling is not exactly a multiple of two, use the 13 samples
|
||||
// filter. This is generally the accepted solution, however, the 13 samples
|
||||
// filter is not correct either when we don't sample at integer coordinates,
|
||||
// but it seems ot create less artifacts.
|
||||
// A better solution might be to use the filter described in
|
||||
// Castaño, 2013, "Shadow Mapping Summary Part 1", which is 5x5 filter with
|
||||
// 9 samples, but works at all coordinates.
|
||||
|
||||
auto hwIn = resources.getTexture(data.in);
|
||||
auto hwOut = resources.getTexture(data.out);
|
||||
auto hwStage = resources.getTexture(data.stage);
|
||||
|
||||
auto const& material9 = getPostProcessMaterial("bloomDownsample9");
|
||||
auto const& material13 = getPostProcessMaterial("bloomDownsample");
|
||||
auto const& material = getPostProcessMaterial("bloomDownsample");
|
||||
auto const* ma = material.getMaterial(mEngine);
|
||||
|
||||
auto* mi9 = material9.getMaterialInstance(mEngine);
|
||||
auto* mi13 = material13.getMaterialInstance(mEngine);
|
||||
FMaterialInstance* mis[] = {
|
||||
ma->createInstance("bloomDownsample-ping"),
|
||||
ma->createInstance("bloomDownsample-pong"),
|
||||
ma->createInstance("bloomDownsample-first"),
|
||||
};
|
||||
|
||||
mi9->setParameter("source", hwOut, {
|
||||
mis[0]->setParameter("source", hwOut, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
mi13->setParameter("source", hwOut, {
|
||||
mis[1]->setParameter("source", hwStage, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
mis[2]->setParameter("source", hwIn, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
mi9->commit(driver);
|
||||
mi13->commit(driver);
|
||||
for (auto* mi : mis) {
|
||||
mi->setParameter("level", 0.0f);
|
||||
mi->setParameter("threshold", inoutBloomOptions.threshold ? 1.0f : 0.0f);
|
||||
mi->setParameter("invHighlight", std::isinf(inoutBloomOptions.highlight)
|
||||
? 0.0f : 1.0f / inoutBloomOptions.highlight);
|
||||
mi->commit(driver);
|
||||
}
|
||||
|
||||
// PipelineState for both materials should be the same
|
||||
const PipelineState pipeline(material9.getPipelineState(mEngine));
|
||||
const PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
|
||||
for (size_t i = 1; i < inoutBloomOptions.levels; i++) {
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i]);
|
||||
{ // first iteration
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[0]);
|
||||
hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR;
|
||||
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
// if downsampling is a multiple of 2 in each dimension we can use the
|
||||
// 9 samples filter.
|
||||
auto vp = resources.getRenderPassInfo(data.outRT[i-1]).params.viewport;
|
||||
auto* const mi = (vp.width & 1 || vp.height & 1) ? mi13 : mi9;
|
||||
mi->use(driver);
|
||||
driver.setMinMaxLevels(hwOut, i - 1, i - 1); // this offsets baseLevel to i-1
|
||||
mis[2]->use(driver);
|
||||
render(hwDstRT, pipeline, driver);
|
||||
}
|
||||
driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1);
|
||||
|
||||
for (size_t i = 1; i < inoutBloomOptions.levels; i++) {
|
||||
const size_t parity = 1u - (i & 0x1u);
|
||||
auto hwDstRT = resources.getRenderPassInfo(parity ? data.outRT[i] : data.stageRT[i]);
|
||||
hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR;
|
||||
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
mis[parity]->setParameter("level", float(i - 1));
|
||||
mis[parity]->commit(driver);
|
||||
mis[parity]->use(driver);
|
||||
render(hwDstRT, pipeline, driver);
|
||||
}
|
||||
|
||||
for (auto& mi : mis) {
|
||||
mEngine.destroy(mi);
|
||||
}
|
||||
});
|
||||
|
||||
// output of bloom downsample pass becomes input of next (flare) pass
|
||||
input = bloomDownsamplePass->out;
|
||||
FrameGraphId<FrameGraphTexture> output = bloomDownsamplePass->out;
|
||||
FrameGraphId<FrameGraphTexture> stage = bloomDownsamplePass->stage;
|
||||
|
||||
// flare pass
|
||||
auto flare = flarePass(fg, input, width, height, outFormat, inoutBloomOptions);
|
||||
auto flare = flarePass(fg, bloomDownsamplePass->out, width, height, outFormat, inoutBloomOptions);
|
||||
|
||||
// upsample phase
|
||||
auto& bloomUpsamplePass = fg.addPass<BloomPassData>("Bloom Upsample",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.out = builder.sample(input);
|
||||
data.out = builder.sample(output);
|
||||
data.stage = builder.sample(stage);
|
||||
for (size_t i = 0; i < inoutBloomOptions.levels; i++) {
|
||||
auto out = builder.createSubresource(data.out, "Bloom Out Texture mip",
|
||||
{ .level = uint8_t(i) });
|
||||
auto staging = builder.createSubresource(data.stage,
|
||||
"Bloom Stage Texture mip", { .level = uint8_t(i) });
|
||||
builder.declareRenderPass(out, &data.outRT[i]);
|
||||
builder.declareRenderPass(staging, &data.stageRT[i]);
|
||||
}
|
||||
},
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
|
||||
auto hwOut = resources.getTexture(data.out);
|
||||
auto hwStage = resources.getTexture(data.stage);
|
||||
auto const& outDesc = resources.getDescriptor(data.out);
|
||||
|
||||
auto const& material = getPostProcessMaterial("bloomUpsample");
|
||||
auto* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("source", hwOut, {
|
||||
auto const* ma = material.getMaterial(mEngine);
|
||||
|
||||
FMaterialInstance* mis[] = {
|
||||
ma->createInstance("bloomUpsample-ping"),
|
||||
ma->createInstance("bloomUpsample-pong"),
|
||||
};
|
||||
|
||||
mis[0]->setParameter("source", hwOut, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST});
|
||||
mi->use(driver);
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
mis[1]->setParameter("source", hwStage, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
|
||||
|
||||
for (size_t j = inoutBloomOptions.levels, i = j - 1; i >= 1; i--, j++) {
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i - 1]);
|
||||
const size_t parity = 1u - (j % 2u);
|
||||
|
||||
auto hwDstRT = resources.getRenderPassInfo(
|
||||
parity ? data.outRT[i - 1] : data.stageRT[i - 1]);
|
||||
hwDstRT.params.flags.discardStart = TargetBufferFlags::NONE; // b/c we'll blend
|
||||
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
auto w = FTexture::valueForLevel(i - 1, outDesc.width);
|
||||
auto h = FTexture::valueForLevel(i - 1, outDesc.height);
|
||||
mi->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h });
|
||||
mi->commit(driver);
|
||||
driver.setMinMaxLevels(hwOut, i, i); // this offsets baseLevel to i
|
||||
mis[parity]->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h });
|
||||
mis[parity]->setParameter("level", float(i));
|
||||
mis[parity]->commit(driver);
|
||||
mis[parity]->use(driver);
|
||||
render(hwDstRT, pipeline, driver);
|
||||
}
|
||||
driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1);
|
||||
|
||||
for (auto& mi : mis) {
|
||||
mEngine.destroy(mi);
|
||||
}
|
||||
|
||||
// Every other level is missing from the out texture, so we need to do
|
||||
// blits to complete the chain.
|
||||
const SamplerMagFilter filter = SamplerMagFilter::NEAREST;
|
||||
for (size_t i = 1; i < inoutBloomOptions.levels; i += 2) {
|
||||
auto in = resources.getRenderPassInfo(data.stageRT[i]);
|
||||
auto out = resources.getRenderPassInfo(data.outRT[i]);
|
||||
driver.blit(TargetBufferFlags::COLOR, out.target, out.params.viewport,
|
||||
in.target, in.params.viewport, filter);
|
||||
}
|
||||
});
|
||||
|
||||
return { bloomUpsamplePass->out, flare };
|
||||
@@ -3051,6 +3036,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
mi->setParameter("level", uint32_t(level));
|
||||
mi->setParameter("layer", uint32_t(layer));
|
||||
mi->setParameter("uvscale", 1.0f / float(dim));
|
||||
mi->commit(driver);
|
||||
|
||||
@@ -292,11 +292,6 @@ private:
|
||||
FrameGraphId<FrameGraphTexture> input, backend::TextureFormat outFormat,
|
||||
BloomOptions& inoutBloomOptions, math::float2 scale) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> downscalePass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
bool threshold, float highlight, bool fireflies) noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material, uint8_t variant,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
+89
-133
@@ -110,7 +110,7 @@ math::mat4f ShadowMap::getPointLightViewMatrix(backend::TextureCubemapFace face,
|
||||
}
|
||||
|
||||
ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
FScene::LightSoa const& lightData, size_t index,
|
||||
const FScene::LightSoa& lightData, size_t index,
|
||||
filament::CameraInfo const& camera,
|
||||
ShadowMapInfo const& shadowMapInfo,
|
||||
SceneInfo const& sceneInfo) noexcept {
|
||||
@@ -131,7 +131,20 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
else params.options.shadowFarHint = dzf * dz + camera.zf;
|
||||
#endif
|
||||
|
||||
const auto direction = params.options.transform * lightData.elementAt<FScene::DIRECTION>(index);
|
||||
// Adjust the camera's projection for the light's shadowFar
|
||||
const mat4f cullingProjection{ [&](auto p) {
|
||||
if (params.options.shadowFar > 0.0f) {
|
||||
float const n = camera.zn;
|
||||
float const f = params.options.shadowFar;
|
||||
// orthographic projection
|
||||
assert_invariant(std::abs(p[2].w) <= std::numeric_limits<float>::epsilon());
|
||||
p[2].z = 2.0f / (n - f);
|
||||
p[3].z = (f + n) / (n - f);
|
||||
}
|
||||
return p;
|
||||
}(camera.cullingProjection) };
|
||||
|
||||
const auto direction = lightData.elementAt<FScene::DIRECTION>(index);
|
||||
|
||||
/*
|
||||
* Compute the light's model matrix
|
||||
@@ -151,7 +164,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
// view frustum vertices in world-space
|
||||
float3 wsViewFrustumVertices[8];
|
||||
const mat4f worldToClipMatrix = camera.cullingProjection * camera.view;
|
||||
const mat4f worldToClipMatrix = cullingProjection * camera.view;
|
||||
const Frustum wsFrustum(worldToClipMatrix);
|
||||
computeFrustumCorners(wsViewFrustumVertices, inverse(worldToClipMatrix), sceneInfo.csNearFar);
|
||||
|
||||
@@ -203,7 +216,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
}
|
||||
|
||||
// Now that we know the znear (-lsLightFrustumBounds.max.z), adjust the light's position such
|
||||
// that znear = 0, this is only needed for VSM, but doesn't hurt PCF.
|
||||
// that znear = 0, this is only need for VSM, but doesn't hurt PCF.
|
||||
const mat4f Mv = getDirectionalLightViewMatrix(direction, direction * -lsLightFrustumBounds.max.z);
|
||||
|
||||
// near / far planes are specified relative to the direction the eye is looking at
|
||||
@@ -227,11 +240,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
const float4 shadowReceiverVolumeBoundingSphere = computeBoundingSphere(
|
||||
wsShadowReceiversVolume.getCorners().data(), 8);
|
||||
|
||||
// in stable mode we simply take the view volume bounding sphere, but we calculate it
|
||||
// in view space, so that it's perfectly stable.
|
||||
float3 vertices[8];
|
||||
computeFrustumCorners(vertices, inverse(camera.cullingProjection), sceneInfo.csNearFar);
|
||||
viewVolumeBoundingSphere = computeBoundingSphere(vertices, 8);
|
||||
// in stable mode we simply take the view volume, bounding sphere
|
||||
viewVolumeBoundingSphere = computeBoundingSphere(wsViewFrustumVertices, 8);
|
||||
|
||||
if (shadowReceiverVolumeBoundingSphere.w < viewVolumeBoundingSphere.w) {
|
||||
|
||||
@@ -310,66 +320,45 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
//
|
||||
// In LiPSM mode, we're using the warped space here.
|
||||
|
||||
float2 s, o;
|
||||
Aabb bounds;
|
||||
if (params.options.stable && viewVolumeBoundingSphere.w > 0) {
|
||||
bounds = compute2DBounds(Mv, viewVolumeBoundingSphere);
|
||||
} else {
|
||||
bounds = compute2DBounds(WLMpMv, wsClippedShadowReceiverVolume.data(), vertexCount);
|
||||
}
|
||||
lsLightFrustumBounds.min.xy = bounds.min.xy;
|
||||
lsLightFrustumBounds.max.xy = bounds.max.xy;
|
||||
|
||||
if (params.options.stable) {
|
||||
if (viewVolumeBoundingSphere.w > 0) {
|
||||
s = 1.0f / viewVolumeBoundingSphere.w;
|
||||
o = mat4f::project(LMpMv * camera.model, viewVolumeBoundingSphere.xyz).xy;
|
||||
} else {
|
||||
Aabb const bounds = compute2DBounds(LMpMv,
|
||||
wsClippedShadowReceiverVolume.data(), vertexCount);
|
||||
if (UTILS_UNLIKELY((bounds.min.x >= bounds.max.x) || (bounds.min.y >= bounds.max.y))) {
|
||||
// this could happen if the only thing visible is a perfectly horizontal or
|
||||
// vertical thin line
|
||||
mHasVisibleShadows = false;
|
||||
return {};
|
||||
}
|
||||
assert_invariant(bounds.min.x < bounds.max.x);
|
||||
assert_invariant(bounds.min.y < bounds.max.y);
|
||||
|
||||
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
|
||||
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
|
||||
|
||||
// Quantize the scale in world-space units. This value can be very small because
|
||||
// if it wasn't for floating-point imprecision, the scale would be a constant.
|
||||
double2 const quantizer = 0.0625;
|
||||
s = 1.0 / (ceil(1.0 / (s * quantizer)) * quantizer);
|
||||
}
|
||||
// in stable mode we can't do anything that can change the scaling of the texture
|
||||
} else {
|
||||
Aabb const bounds = compute2DBounds(WLMpMv,
|
||||
wsClippedShadowReceiverVolume.data(), vertexCount);
|
||||
lsLightFrustumBounds.min.xy = bounds.min.xy;
|
||||
lsLightFrustumBounds.max.xy = bounds.max.xy;
|
||||
// For directional lights, we further constraint the light frustum to the
|
||||
// intersection of the shadow casters & shadow receivers in light-space.
|
||||
// ** This relies on the 1-texel shadow map border **
|
||||
|
||||
if (engine.debug.shadowmap.focus_shadowcasters) {
|
||||
intersectWithShadowCasters(&lsLightFrustumBounds, WLMpMv, wsShadowCastersVolume);
|
||||
intersectWithShadowCasters(lsLightFrustumBounds, WLMpMv, wsShadowCastersVolume);
|
||||
}
|
||||
if (UTILS_UNLIKELY((lsLightFrustumBounds.min.x >= lsLightFrustumBounds.max.x) ||
|
||||
(lsLightFrustumBounds.min.y >= lsLightFrustumBounds.max.y))) {
|
||||
// this could happen if the only thing visible is a perfectly horizontal or
|
||||
// vertical thin line
|
||||
mHasVisibleShadows = false;
|
||||
return {};
|
||||
}
|
||||
assert_invariant(lsLightFrustumBounds.min.x < lsLightFrustumBounds.max.x);
|
||||
assert_invariant(lsLightFrustumBounds.min.y < lsLightFrustumBounds.max.y);
|
||||
|
||||
s = 2.0f / float2(lsLightFrustumBounds.max.xy - lsLightFrustumBounds.min.xy);
|
||||
o = float2(lsLightFrustumBounds.max.xy + lsLightFrustumBounds.min.xy) * 0.5f;
|
||||
|
||||
// TODO: we could quantize `s` here to give some stability when lispsm is disabled,
|
||||
// however, the quantization paramater should probably be user settable.
|
||||
}
|
||||
|
||||
// adjust offset for scale
|
||||
o = -s * o;
|
||||
if (UTILS_UNLIKELY((lsLightFrustumBounds.min.x >= lsLightFrustumBounds.max.x) ||
|
||||
(lsLightFrustumBounds.min.y >= lsLightFrustumBounds.max.y))) {
|
||||
// this could happen if the only thing visible is a perfectly horizontal or
|
||||
// vertical thin line
|
||||
mHasVisibleShadows = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
assert_invariant(lsLightFrustumBounds.min.x < lsLightFrustumBounds.max.x);
|
||||
assert_invariant(lsLightFrustumBounds.min.y < lsLightFrustumBounds.max.y);
|
||||
|
||||
// compute focus scale and offset
|
||||
float2 s = 2.0f / float2(lsLightFrustumBounds.max.xy - lsLightFrustumBounds.min.xy);
|
||||
float2 o = -s * float2(lsLightFrustumBounds.max.xy + lsLightFrustumBounds.min.xy) * 0.5f;
|
||||
|
||||
if (params.options.stable) {
|
||||
snapLightFrustum(s, o, LMpMv,
|
||||
sceneInfo.wsShadowCastersVolume.center(), shadowMapInfo.shadowDimension);
|
||||
// Use the world origin as reference point, fixed w.r.t. the camera
|
||||
snapLightFrustum(s, o, Mv, camera.worldOrigin[3].xyz,
|
||||
1.0f / float(shadowMapInfo.shadowDimension));
|
||||
}
|
||||
|
||||
const mat4f F(mat4f::row_major_init {
|
||||
@@ -388,7 +377,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
// Computes St the transform to use in the shader to access the shadow map texture
|
||||
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||
const backend::Viewport viewport = getViewport();
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, viewport);
|
||||
const mat4f St = math::highPrecisionMultiply(Mt, S);
|
||||
|
||||
ShadowMap::ShaderParameters shaderParameters;
|
||||
@@ -410,7 +400,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
shaderParameters.lightSpace = computeVsmLightSpaceMatrix(St, Mv, znear, zfar);
|
||||
}
|
||||
|
||||
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
|
||||
shaderParameters.scissorNormalized = getViewportNormalized(shadowMapInfo);
|
||||
|
||||
// We apply the constant bias in world space (as opposed to light-space) to account
|
||||
// for perspective and lispsm shadow maps. This also allows us to do this at zero-cost
|
||||
@@ -443,8 +433,9 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
|
||||
assert_invariant(shadowMapInfo.textureDimension == mOptions->mapSize);
|
||||
|
||||
// Final shadow transform
|
||||
const backend::Viewport viewport = getViewport();
|
||||
const mat4f S = math::highPrecisionMultiply(Mp, Mv);
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, viewport);
|
||||
const mat4f St = math::highPrecisionMultiply(Mt, S);
|
||||
|
||||
// TODO: focus projection
|
||||
@@ -471,7 +462,7 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
|
||||
shaderParameters.lightSpace = computeVsmLightSpaceMatrix(St, Mv, nearPlane, farPlane);
|
||||
}
|
||||
|
||||
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
|
||||
shaderParameters.scissorNormalized = getViewportNormalized(shadowMapInfo);
|
||||
|
||||
const float3 direction = -transpose(Mv)[2].xyz;
|
||||
const float constantBias = shadowMapInfo.vsm ? 0.0f : params.options.constantBias;
|
||||
@@ -768,12 +759,20 @@ Aabb ShadowMap::compute2DBounds(const mat4f& lightView,
|
||||
return bounds;
|
||||
}
|
||||
|
||||
void ShadowMap::intersectWithShadowCasters(Aabb* UTILS_RESTRICT lightFrustum,
|
||||
Aabb ShadowMap::compute2DBounds(const mat4f& lightView, float4 const& sphere) noexcept {
|
||||
// this assumes a rigid body transform
|
||||
float4 s;
|
||||
s.xyz = (lightView * float4{sphere.xyz, 1.0f}).xyz;
|
||||
s.w = sphere.w;
|
||||
return Aabb{s.xyz - s.w, s.xyz + s.w};
|
||||
}
|
||||
|
||||
void ShadowMap::intersectWithShadowCasters(Aabb& UTILS_RESTRICT lightFrustum,
|
||||
mat4f const& lightView, Aabb const& wsShadowCastersVolume) noexcept {
|
||||
|
||||
// construct the Focus transform (scale + offset)
|
||||
const float2 s = 2.0f / float2(lightFrustum->max.xy - lightFrustum->min.xy);
|
||||
const float2 o = -s * float2(lightFrustum->max.xy + lightFrustum->min.xy) * 0.5f;
|
||||
const float2 s = 2.0f / float2(lightFrustum.max.xy - lightFrustum.min.xy);
|
||||
const float2 o = -s * float2(lightFrustum.max.xy + lightFrustum.min.xy) * 0.5f;
|
||||
const mat4f F(mat4f::row_major_init {
|
||||
s.x, 0.0f, 0.0f, o.x,
|
||||
0.0f, s.y, 0.0f, o.y,
|
||||
@@ -799,8 +798,8 @@ void ShadowMap::intersectWithShadowCasters(Aabb* UTILS_RESTRICT lightFrustum,
|
||||
Aabb const box = compute2DBounds(lightView, wsClippedShadowCasterVolumeVertices.data(), vertexCount);
|
||||
|
||||
// intersect shadow-caster and current light frustum bounds
|
||||
lightFrustum->min.xy = max(box.min.xy, lightFrustum->min.xy);
|
||||
lightFrustum->max.xy = min(box.max.xy, lightFrustum->max.xy);
|
||||
lightFrustum.min.xy = max(box.min.xy, lightFrustum.min.xy);
|
||||
lightFrustum.max.xy = min(box.max.xy, lightFrustum.max.xy);
|
||||
}
|
||||
|
||||
void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
|
||||
@@ -826,39 +825,21 @@ void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
|
||||
}
|
||||
|
||||
void ShadowMap::snapLightFrustum(float2& s, float2& o,
|
||||
mat4f const& Mv, double3 wsSnapCoords, int2 resolution) noexcept {
|
||||
mat4f const& Mv, float3 worldOrigin, float2 shadowMapResolution) noexcept {
|
||||
|
||||
auto proj = [](mat4 m, double3 v) -> double3 {
|
||||
// for directional light p.w == 1, exactly
|
||||
auto p = m * v;
|
||||
assert_invariant(p.w == 1.0);
|
||||
return p.xyz;
|
||||
auto fmod = [](float2 x, float2 y) -> float2 {
|
||||
auto mod = [](float x, float y) -> float { return std::fmod(x, y); };
|
||||
return float2{ mod(x[0], y[0]), mod(x[1], y[1]) };
|
||||
};
|
||||
|
||||
auto fract = [](auto v) {
|
||||
using namespace std;
|
||||
using T = decltype(v);
|
||||
return fmod(v, T{1});
|
||||
};
|
||||
|
||||
const mat4 F(mat4::row_major_init {
|
||||
s.x, 0.0, 0.0, o.x,
|
||||
0.0, s.y, 0.0, o.y,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
});
|
||||
|
||||
// The (resolution * 0.5) comes from Mv having a NDC in the range -1,1 (so a range of 2).
|
||||
|
||||
// focused light-space
|
||||
mat4 const FMv{ F * Mv };
|
||||
// This snaps the shadow map bounds to texels.
|
||||
// The 2.0 comes from Mv having a NDC in the range -1,1 (so a range of 2).
|
||||
const float2 r = 2.0f * shadowMapResolution;
|
||||
o -= fmod(o, r);
|
||||
|
||||
// This offsets the texture coordinates, so it has a fixed offset w.r.t the world
|
||||
double2 const lsOrigin = proj(FMv, wsSnapCoords).xy;
|
||||
double2 const d = (fract(lsOrigin * resolution * 0.5) * 2.0) / resolution;
|
||||
|
||||
// adjust offset
|
||||
o -= d;
|
||||
const float2 lsOrigin = mat4f::project(Mv, worldOrigin).xy * s;
|
||||
o -= fmod(lsOrigin, r);
|
||||
}
|
||||
|
||||
size_t ShadowMap::intersectFrustumWithBox(
|
||||
@@ -1061,7 +1042,7 @@ bool ShadowMap::intersectSegmentWithPlanarQuad(float3& UTILS_RESTRICT p,
|
||||
}
|
||||
|
||||
float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) noexcept {
|
||||
uint16_t shadowDimension) const noexcept {
|
||||
// The Jacobian of the transformation from texture-to-world is the matrix itself for
|
||||
// orthographic projections. We just need to inverse worldToShadowTexture,
|
||||
// which is guaranteed to be orthographic.
|
||||
@@ -1076,7 +1057,7 @@ float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture,
|
||||
}
|
||||
|
||||
float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
|
||||
uint16_t shadowDimension) noexcept {
|
||||
uint16_t shadowDimension) const noexcept {
|
||||
// Here we compute the Jacobian of inverse(MbMtF * Wp).
|
||||
// The expression below has been computed with Mathematica. However, it's not very hard,
|
||||
// albeit error-prone, to do it by hand because MbMtF is a linear transform.
|
||||
@@ -1097,7 +1078,7 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
|
||||
|
||||
constexpr bool JACOBIAN_ESTIMATE = false;
|
||||
if constexpr (JACOBIAN_ESTIMATE) {
|
||||
// This estimates the Jacobian -- this is a lot heavier. This is mostly for reference
|
||||
// this estimates the Jacobian -- this is a lot heavier. This is mostly for reference
|
||||
// and testing.
|
||||
const mat4f Si(inverse(MbMtF * Wp));
|
||||
const float3 p0 = mat4f::project(Si, p);
|
||||
@@ -1236,58 +1217,33 @@ void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getViewport() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp).
|
||||
// We set a viewport with a 1-texel border for when we index outside the
|
||||
// texture. This can only happen for the directional light when "focus shadow casters is used".
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
return { border, border, dim - 2u * border, dim - 2u * border };
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getScissor() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp), so we
|
||||
// don't scissor the border, so it gets filled with correct neighboring texels.
|
||||
// We set a viewport with a 1-texel border for when we index outside the
|
||||
// texture. This can only happen for the directional light when "focus shadow casters is used".
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
return { border, border, dim - 2u * border, dim - 2u * border };
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
default:
|
||||
return { 0, 0, dim, dim };
|
||||
}
|
||||
}
|
||||
|
||||
math::float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
float border; // shadowmap border in texels
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
// For directional lights, we need to allow the sampling to reach the border, it
|
||||
// happens when "focus shadow casters" is used for instance.
|
||||
border = 0.5f;
|
||||
break;
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
// For spot and point light, this is equal to the viewport. i.e. the valid
|
||||
// texels are inside the viewport (w/ 1-texel border), the border will be used
|
||||
// for bilinear filtering.
|
||||
border = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
float const texel = 1.0f / float(shadowMapInfo.atlasDimension);
|
||||
float const dim = float(mOptions->mapSize);
|
||||
float const l = border;
|
||||
float const b = border;
|
||||
float const w = dim - 2.0f * border;
|
||||
float const h = dim - 2.0f * border;
|
||||
float4 const v = float4{ l, b, l + w, b + h } * texel;
|
||||
math::float4 ShadowMap::getViewportNormalized(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
const auto [l, b, w, h] = getViewport();
|
||||
const float texel = 1.0f / float(shadowMapInfo.atlasDimension);
|
||||
const float4 v = float4{ l, b, l + w, b + h } * texel;
|
||||
if (shadowMapInfo.textureSpaceFlipped) {
|
||||
// this is equivalent to calling uvToRenderTargetUV() in the shader *after* clamping
|
||||
// texture coordinates to this normalized viewport.
|
||||
|
||||
@@ -222,7 +222,7 @@ private:
|
||||
const math::float3& dir);
|
||||
|
||||
static inline void snapLightFrustum(math::float2& s, math::float2& o,
|
||||
math::mat4f const& Mv, math::double3 wsSnapCoords, math::int2 resolution) noexcept;
|
||||
math::mat4f const& Mv, math::float3 worldOrigin, math::float2 shadowMapResolution) noexcept;
|
||||
|
||||
static inline void computeFrustumCorners(math::float3* out,
|
||||
const math::mat4f& projectionViewInverse, math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
|
||||
@@ -240,8 +240,10 @@ private:
|
||||
static inline Aabb compute2DBounds(const math::mat4f& lightView,
|
||||
math::float3 const* wsVertices, size_t count) noexcept;
|
||||
|
||||
static inline void intersectWithShadowCasters(Aabb* lightFrustum,
|
||||
math::mat4f const& lightView,
|
||||
static inline Aabb compute2DBounds(const math::mat4f& lightView,
|
||||
math::float4 const& sphere) noexcept;
|
||||
|
||||
static inline void intersectWithShadowCasters(Aabb& lightFrustum, const math::mat4f& lightView,
|
||||
Aabb const& wsShadowCastersVolume) noexcept;
|
||||
|
||||
static inline math::float2 computeNearFarOfWarpSpace(math::mat4f const& lightView,
|
||||
@@ -279,13 +281,13 @@ private:
|
||||
static math::mat4f computeVsmLightSpaceMatrix(const math::mat4f& lightSpacePcf,
|
||||
const math::mat4f& Mv, float znear, float zfar) noexcept;
|
||||
|
||||
math::float4 getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||
math::float4 getViewportNormalized(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||
|
||||
static float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) noexcept;
|
||||
float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) const noexcept;
|
||||
|
||||
static float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
|
||||
uint16_t shadowDimension) noexcept;
|
||||
float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
|
||||
uint16_t shadowDimension) const noexcept;
|
||||
|
||||
static constexpr const Segment sBoxSegments[12] = {
|
||||
{ 0, 1 }, { 1, 3 }, { 3, 2 }, { 2, 0 },
|
||||
|
||||
@@ -435,9 +435,8 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
}
|
||||
|
||||
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept {
|
||||
|
||||
FScene* scene = view.getScene();
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
@@ -445,24 +444,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
FLightManager::ShadowOptions const& options = lcm.getShadowOptions(directionalLight);
|
||||
FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight);
|
||||
|
||||
// Adjust the camera's projection for the light's shadowFar
|
||||
|
||||
cameraInfo.zf = params.options.shadowFar > 0.0f ? params.options.shadowFar : cameraInfo.zf;
|
||||
if (UTILS_UNLIKELY(params.options.shadowFar > 0.0f)) {
|
||||
cameraInfo.zf = params.options.shadowFar;
|
||||
float const n = cameraInfo.zn;
|
||||
float const f = cameraInfo.zf;
|
||||
if (std::abs(cameraInfo.cullingProjection[2].w) > std::numeric_limits<float>::epsilon()) {
|
||||
// perspective projection
|
||||
cameraInfo.cullingProjection[2].z = (f + n) / (n - f);
|
||||
cameraInfo.cullingProjection[3].z = (2 * f * n) / (n - f);
|
||||
} else {
|
||||
// orthographic projection
|
||||
cameraInfo.cullingProjection[2].z = 2.0f / (n - f);
|
||||
cameraInfo.cullingProjection[3].z = (f + n) / (n - f);
|
||||
}
|
||||
}
|
||||
|
||||
const ShadowMap::ShadowMapInfo shadowMapInfo{
|
||||
.atlasDimension = mTextureAtlasRequirements.size,
|
||||
.textureDimension = uint16_t(options.mapSize),
|
||||
@@ -478,7 +459,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
// entire camera frustum, as if we only had a single cascade.
|
||||
ShadowMap& shadowMap = *mCascadeShadowMaps[0];
|
||||
|
||||
const auto direction = options.transform * lightData.elementAt<FScene::DIRECTION>(0);
|
||||
const auto direction = lightData.elementAt<FScene::DIRECTION>(0);
|
||||
|
||||
// We compute the directional light's model matrix using the origin's as the light position.
|
||||
// The choice of the light's origin initially doesn't matter for a directional light.
|
||||
@@ -523,13 +504,19 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
splitPercentages[i] = options.cascadeSplitPositions[i - 1];
|
||||
}
|
||||
|
||||
const CascadeSplits splits({
|
||||
const CascadeSplits::Params p{
|
||||
.proj = cameraInfo.cullingProjection,
|
||||
.near = vsNear,
|
||||
.far = vsFar,
|
||||
.cascadeCount = cascadeCount,
|
||||
.splitPositions = splitPercentages
|
||||
});
|
||||
};
|
||||
if (p != mCascadeSplitParams) {
|
||||
mCascadeSplits = CascadeSplits{ p };
|
||||
mCascadeSplitParams = p;
|
||||
}
|
||||
|
||||
const CascadeSplits& splits = mCascadeSplits;
|
||||
|
||||
// The split positions uniform is a float4. To save space, we chop off the first split position
|
||||
// (which is the near plane, and doesn't need to be communicated to the shaders).
|
||||
@@ -543,7 +530,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
|
||||
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
||||
|
||||
// When computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
||||
// when computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0);
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
|
||||
private:
|
||||
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept;
|
||||
|
||||
ShadowMapManager::ShadowTechnique updateSpotShadowMaps(FEngine& engine,
|
||||
@@ -145,8 +145,17 @@ private:
|
||||
float far = 0.0f;
|
||||
size_t cascadeCount = 1;
|
||||
std::array<float, SPLIT_COUNT> splitPositions = { 0.0f };
|
||||
|
||||
bool operator!=(const Params& rhs) const {
|
||||
return proj != rhs.proj ||
|
||||
near != rhs.near ||
|
||||
far != rhs.far ||
|
||||
cascadeCount != rhs.cascadeCount ||
|
||||
splitPositions != rhs.splitPositions;
|
||||
}
|
||||
};
|
||||
|
||||
CascadeSplits() noexcept : CascadeSplits(Params{}) {}
|
||||
explicit CascadeSplits(Params const& params) noexcept;
|
||||
|
||||
// Split positions in world-space.
|
||||
@@ -177,6 +186,9 @@ private:
|
||||
|
||||
SoftShadowOptions mSoftShadowOptions;
|
||||
|
||||
CascadeSplits::Params mCascadeSplitParams;
|
||||
CascadeSplits mCascadeSplits;
|
||||
|
||||
mutable TypedUniformBuffer<ShadowUib> mShadowUb;
|
||||
backend::Handle<backend::HwBufferObject> mShadowUbh;
|
||||
|
||||
@@ -192,8 +204,8 @@ private:
|
||||
utils::FixedCapacityVector<ShadowMap*>::with_capacity(
|
||||
CONFIG_MAX_SHADOWMAPS - CONFIG_MAX_SHADOW_CASCADES) };
|
||||
|
||||
// Inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly.
|
||||
// Because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations.
|
||||
// inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly.
|
||||
// because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations.
|
||||
// Each ShadowMap is currently 40 bytes (total of 2.5KB for 64 shadow maps)
|
||||
using ShadowMapStorage = std::aligned_storage<sizeof(ShadowMap), alignof(ShadowMap)>::type;
|
||||
std::array<ShadowMapStorage, CONFIG_MAX_SHADOWMAPS> mShadowMapCache;
|
||||
|
||||
@@ -127,12 +127,12 @@ public:
|
||||
}
|
||||
|
||||
bool isSpotLight(Instance i) const noexcept {
|
||||
Type const type = getType(i);
|
||||
Type type = getType(i);
|
||||
return type == Type::FOCUSED_SPOT || type == Type::SPOT;
|
||||
}
|
||||
|
||||
bool isDirectionalLight(Instance i) const noexcept {
|
||||
Type const type = getType(i);
|
||||
Type type = getType(i);
|
||||
return type == Type::DIRECTIONAL || type == Type::SUN;
|
||||
}
|
||||
|
||||
|
||||
@@ -1080,9 +1080,8 @@ void FView::setSoftShadowOptions(SoftShadowOptions options) noexcept {
|
||||
|
||||
void FView::setBloomOptions(BloomOptions options) noexcept {
|
||||
options.dirtStrength = math::saturate(options.dirtStrength);
|
||||
options.resolution = math::clamp(options.resolution, 2u, 2048u);
|
||||
options.levels = math::clamp(options.levels, uint8_t(1),
|
||||
FTexture::maxLevelCount(options.resolution));
|
||||
options.levels = math::clamp(options.levels, uint8_t(1), uint8_t(11));
|
||||
options.resolution = math::clamp(options.resolution, 1u << options.levels, 2048u);
|
||||
options.anamorphism = math::clamp(options.anamorphism, 1.0f/32.0f, 32.0f);
|
||||
options.highlight = std::max(10.0f, options.highlight);
|
||||
mBloomOptions = options;
|
||||
|
||||
@@ -5,6 +5,18 @@ material {
|
||||
type : sampler2d,
|
||||
name : source,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : level
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : threshold
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : invHighlight
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -25,43 +37,85 @@ fragment {
|
||||
|
||||
void dummy(){}
|
||||
|
||||
void threshold(inout vec3 c) {
|
||||
// threshold everything below 1.0
|
||||
c = max(vec3(0.0), c - 1.0);
|
||||
// crush everything above 1
|
||||
highp float f = max3(c);
|
||||
c *= 1.0 / (1.0 + f * materialParams.invHighlight);
|
||||
}
|
||||
|
||||
vec3 box4x4(vec3 s0, vec3 s1, vec3 s2, vec3 s3) {
|
||||
return (s0 + s1 + s2 + s3) * 0.25;
|
||||
}
|
||||
|
||||
vec3 box4x4Reinhard(vec3 s0, vec3 s1, vec3 s2, vec3 s3) {
|
||||
float w0 = 1.0 / (1.0 + max3(s0));
|
||||
float w1 = 1.0 / (1.0 + max3(s1));
|
||||
float w2 = 1.0 / (1.0 + max3(s2));
|
||||
float w3 = 1.0 / (1.0 + max3(s3));
|
||||
return (s0 * w0 + s1 * w1 + s2 * w2 + s3 * w3) * (1.0 / (w0 + w1 + w2 + w3));
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
float lod = materialParams.level;
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
// see SIGGRAPH 2014: Advances in Real-Time Rendering
|
||||
// "Next Generation Post-Processing in Call of Duty Advanced Warfare"
|
||||
// Jorge Jimenez
|
||||
vec3 c = textureLod(materialParams_source, uv, 0.0).rgb;
|
||||
vec3 c = textureLod(materialParams_source, uv, lod).rgb;
|
||||
|
||||
// The offsets below are in "source" texture space
|
||||
vec3 lt = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-1, -1)).rgb;
|
||||
vec3 rt = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 1, -1)).rgb;
|
||||
vec3 rb = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 1, 1)).rgb;
|
||||
vec3 lb = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-1, 1)).rgb;
|
||||
vec3 lt = textureLodOffset(materialParams_source, uv, lod, ivec2(-1, -1)).rgb;
|
||||
vec3 rt = textureLodOffset(materialParams_source, uv, lod, ivec2( 1, -1)).rgb;
|
||||
vec3 rb = textureLodOffset(materialParams_source, uv, lod, ivec2( 1, 1)).rgb;
|
||||
vec3 lb = textureLodOffset(materialParams_source, uv, lod, ivec2(-1, 1)).rgb;
|
||||
|
||||
vec3 lt2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, -2)).rgb;
|
||||
vec3 rt2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, -2)).rgb;
|
||||
vec3 rb2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, 2)).rgb;
|
||||
vec3 lb2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, 2)).rgb;
|
||||
vec3 lt2 = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, -2)).rgb;
|
||||
vec3 rt2 = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, -2)).rgb;
|
||||
vec3 rb2 = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, 2)).rgb;
|
||||
vec3 lb2 = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, 2)).rgb;
|
||||
|
||||
vec3 l = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, 0)).rgb;
|
||||
vec3 t = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 0, -2)).rgb;
|
||||
vec3 r = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, 0)).rgb;
|
||||
vec3 b = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 0, 2)).rgb;
|
||||
vec3 l = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, 0)).rgb;
|
||||
vec3 t = textureLodOffset(materialParams_source, uv, lod, ivec2( 0, -2)).rgb;
|
||||
vec3 r = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, 0)).rgb;
|
||||
vec3 b = textureLodOffset(materialParams_source, uv, lod, ivec2( 0, 2)).rgb;
|
||||
|
||||
// five h4x4 boxes
|
||||
vec3 c0, c1;
|
||||
|
||||
// common case
|
||||
c0 = box4x4(lt, rt, rb, lb);
|
||||
c1 = box4x4(c, l, t, lt2);
|
||||
c1 += box4x4(c, r, t, rt2);
|
||||
c1 += box4x4(c, r, b, rb2);
|
||||
c1 += box4x4(c, l, b, lb2);
|
||||
if (materialParams.level <= 0.5) {
|
||||
if (materialParams.threshold > 0.0) {
|
||||
// Threshold the first level blur
|
||||
threshold(c);
|
||||
threshold(lt);
|
||||
threshold(rt);
|
||||
threshold(rb);
|
||||
threshold(lb);
|
||||
threshold(lt2);
|
||||
threshold(rt2);
|
||||
threshold(rb2);
|
||||
threshold(lb2);
|
||||
threshold(l);
|
||||
threshold(t);
|
||||
threshold(r);
|
||||
threshold(b);
|
||||
}
|
||||
// Also apply fireflies (flickering) filtering
|
||||
c0 = box4x4Reinhard(lt, rt, rb, lb);
|
||||
c1 = box4x4Reinhard(c, l, t, lt2);
|
||||
c1 += box4x4Reinhard(c, r, t, rt2);
|
||||
c1 += box4x4Reinhard(c, r, b, rb2);
|
||||
c1 += box4x4Reinhard(c, l, b, lb2);
|
||||
} else {
|
||||
// common case
|
||||
c0 = box4x4(lt, rt, rb, lb);
|
||||
c1 = box4x4(c, l, t, lt2);
|
||||
c1 += box4x4(c, r, t, rt2);
|
||||
c1 += box4x4(c, r, b, rb2);
|
||||
c1 += box4x4(c, l, b, lb2);
|
||||
}
|
||||
|
||||
// weighted average of the five boxes
|
||||
postProcess.color.rgb = c0 * 0.5 + c1 * 0.125;
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
material {
|
||||
name : bloomDownsample2x,
|
||||
parameters : [
|
||||
{
|
||||
type : sampler2d,
|
||||
name : source,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : level
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : threshold
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : fireflies
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : invHighlight
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
domain : postprocess,
|
||||
depthWrite : false,
|
||||
depthCulling : false
|
||||
}
|
||||
|
||||
vertex {
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV);
|
||||
}
|
||||
}
|
||||
|
||||
fragment {
|
||||
|
||||
void dummy(){}
|
||||
|
||||
void threshold(inout vec3 c) {
|
||||
// threshold everything below 1.0
|
||||
c = max(vec3(0.0), c - 1.0);
|
||||
// crush everything above 1
|
||||
highp float f = max3(c);
|
||||
c *= 1.0 / (1.0 + f * materialParams.invHighlight);
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
float lod = materialParams.level;
|
||||
|
||||
highp vec2 size = vec2(textureSize(materialParams_source, int(lod)));
|
||||
highp vec2 texelSize = vec2(1.0) / size;
|
||||
|
||||
// Castaño, 2013, "Shadow Mapping Summary Part 1"
|
||||
// 3x3 gaussian filter with 4 linear samples
|
||||
vec2 offset = vec2(0.5);
|
||||
highp vec2 uv = (variable_vertex.xy * size) + offset;
|
||||
highp vec2 base = (floor(uv) - offset) * texelSize;
|
||||
highp vec2 st = fract(uv);
|
||||
vec2 uw = vec2(3.0 - 2.0 * st.x, 1.0 + 2.0 * st.x);
|
||||
vec2 vw = vec2(3.0 - 2.0 * st.y, 1.0 + 2.0 * st.y);
|
||||
highp vec2 u = vec2((2.0 - st.x) / uw.x - 1.0, st.x / uw.y + 1.0) * texelSize.x;
|
||||
highp vec2 v = vec2((2.0 - st.y) / vw.x - 1.0, st.y / vw.y + 1.0) * texelSize.y;
|
||||
vec3 c0 = textureLod(materialParams_source, base + vec2(u.x, v.x), lod).rgb;
|
||||
vec3 c1 = textureLod(materialParams_source, base + vec2(u.y, v.x), lod).rgb;
|
||||
vec3 c2 = textureLod(materialParams_source, base + vec2(u.x, v.y), lod).rgb;
|
||||
vec3 c3 = textureLod(materialParams_source, base + vec2(u.y, v.y), lod).rgb;
|
||||
|
||||
float w0 = uw.x * vw.x * (1.0 / 16.0);
|
||||
float w1 = uw.y * vw.x * (1.0 / 16.0);
|
||||
float w2 = uw.x * vw.y * (1.0 / 16.0);
|
||||
float w3 = uw.y * vw.y * (1.0 / 16.0);
|
||||
|
||||
if (materialParams.fireflies > 0.0) {
|
||||
w0 /= (1.0 + max3(c0));
|
||||
w1 /= (1.0 + max3(c1));
|
||||
w2 /= (1.0 + max3(c2));
|
||||
w3 /= (1.0 + max3(c3));
|
||||
float w = 1.0 / (w0 + w1 + w2 + w3);
|
||||
w0 *= w;
|
||||
w1 *= w;
|
||||
w2 *= w;
|
||||
w3 *= w;
|
||||
}
|
||||
|
||||
vec3 c = c0 * w0 + c1 * w1 + c2 * w2 + c3 * w3;
|
||||
|
||||
if (materialParams.threshold > 0.0) {
|
||||
threshold(c);
|
||||
}
|
||||
|
||||
postProcess.color.rgb = c;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
material {
|
||||
name : bloomDownsample9,
|
||||
parameters : [
|
||||
{
|
||||
type : sampler2d,
|
||||
name : source,
|
||||
precision: medium
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
domain : postprocess,
|
||||
depthWrite : false,
|
||||
depthCulling : false
|
||||
}
|
||||
|
||||
vertex {
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV);
|
||||
}
|
||||
}
|
||||
|
||||
fragment {
|
||||
|
||||
void dummy(){}
|
||||
|
||||
// see https://www.shadertoy.com/view/cslczj
|
||||
// 6x6 downsampling kernel implemented via 9 bilinear samples
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
highp vec2 size = vec2(1.0) / vec2(textureSize(materialParams_source, 0));
|
||||
|
||||
float o = 1.5 + 0.261629;
|
||||
float wa = 7.46602 / 32.0;
|
||||
float wb = 1.0 - wa * 2.0;
|
||||
float wab = wa * wb;
|
||||
float waa = wa * wa;
|
||||
float wbb = wb * wb;
|
||||
|
||||
size *= o;
|
||||
|
||||
vec3 c = textureLod(materialParams_source, uv + vec2(0.0) , 0.0).rgb;
|
||||
vec3 l = textureLod(materialParams_source, uv + vec2(-size.x, 0.0), 0.0).rgb;
|
||||
vec3 r = textureLod(materialParams_source, uv + vec2( size.x, 0.0), 0.0).rgb;
|
||||
vec3 b = textureLod(materialParams_source, uv + vec2( 0.0,-size.y), 0.0).rgb;
|
||||
vec3 t = textureLod(materialParams_source, uv + vec2( 0.0, size.y), 0.0).rgb;
|
||||
vec3 lb = textureLod(materialParams_source, uv + vec2(-size.x,-size.y), 0.0).rgb;
|
||||
vec3 rb = textureLod(materialParams_source, uv + vec2( size.x,-size.y), 0.0).rgb;
|
||||
vec3 lt = textureLod(materialParams_source, uv + vec2(-size.x, size.y), 0.0).rgb;
|
||||
vec3 rt = textureLod(materialParams_source, uv + vec2( size.x, size.y), 0.0).rgb;
|
||||
|
||||
postProcess.color.rgb =
|
||||
(c * wbb +
|
||||
(l * wab +
|
||||
(r * wab +
|
||||
(b * wab +
|
||||
(t * wab +
|
||||
(lb * waa +
|
||||
(rb * waa +
|
||||
(lt * waa +
|
||||
(rt * waa)))))))));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,10 @@ material {
|
||||
type : float4,
|
||||
name : resolution,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : level
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -30,15 +34,16 @@ fragment {
|
||||
void dummy(){}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
float lod = materialParams.level;
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
#if FILAMENT_QUALITY < FILAMENT_QUALITY_HIGH
|
||||
highp vec4 d = vec4(materialParams.resolution.zw, -materialParams.resolution.zw) * 0.5;
|
||||
vec3 c;
|
||||
c = textureLod(materialParams_source, uv + d.zw, 0.0).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xw, 0.0).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xy, 0.0).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.zy, 0.0).rgb;
|
||||
c = textureLod(materialParams_source, uv + d.zw, lod).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xw, lod).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xy, lod).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.zy, lod).rgb;
|
||||
postProcess.color.rgb = c * 0.25;
|
||||
#else
|
||||
// see SIGGRAPH 2014: Advances in Real-Time Rendering
|
||||
@@ -47,15 +52,15 @@ fragment {
|
||||
const float radius = 1.0;
|
||||
highp vec4 d = vec4(materialParams.resolution.zw, -materialParams.resolution.zw) * radius;
|
||||
vec3 c0, c1;
|
||||
c0 = textureLod(materialParams_source, uv + d.zw, 0.0).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xw, 0.0).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xy, 0.0).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.zy, 0.0).rgb;
|
||||
c0 += 4.0 * textureLod(materialParams_source, uv, 0.0).rgb;
|
||||
c1 = textureLod(materialParams_source, uv + vec2(d.z, 0.0), 0.0).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(0.0, d.w), 0.0).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(d.x, 0.0), 0.0).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2( 0.0, d.y), 0.0).rgb;
|
||||
c0 = textureLod(materialParams_source, uv + d.zw, lod).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xw, lod).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xy, lod).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.zy, lod).rgb;
|
||||
c0 += 4.0 * textureLod(materialParams_source, uv, lod).rgb;
|
||||
c1 = textureLod(materialParams_source, uv + vec2(d.z, 0.0), lod).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(0.0, d.w), lod).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(d.x, 0.0), lod).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2( 0.0, d.y), lod).rgb;
|
||||
postProcess.color.rgb = (c0 + 2.0 * c1) * (1.0 / 16.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ material {
|
||||
name : coc,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : mip
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : weightScale
|
||||
@@ -61,18 +65,19 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
|
||||
// the bilateral weights need to be scaled by to match the lower resolution
|
||||
float weightScale = materialParams.weightScale;
|
||||
float mip = float(materialParams.mip);
|
||||
|
||||
vec4 s01 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(0, 1));
|
||||
vec4 s11 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(1, 1));
|
||||
vec4 s10 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(1, 0));
|
||||
vec4 s00 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(0, 0));
|
||||
vec4 s01 = textureLodOffset(materialParams_color, uv, mip, ivec2(0, 1));
|
||||
vec4 s11 = textureLodOffset(materialParams_color, uv, mip, ivec2(1, 1));
|
||||
vec4 s10 = textureLodOffset(materialParams_color, uv, mip, ivec2(1, 0));
|
||||
vec4 s00 = textureLodOffset(materialParams_color, uv, mip, ivec2(0, 0));
|
||||
|
||||
// fetch the 4 corresponding CoC (textureGather with LOD doesn't exist)
|
||||
vec4 c;
|
||||
c[0] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(0, 1)).r;
|
||||
c[1] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(1, 1)).r;
|
||||
c[2] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(1, 0)).r;
|
||||
c[3] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(0, 0)).r;
|
||||
c[0] = textureLodOffset(materialParams_coc, uv, mip, ivec2(0, 1)).r;
|
||||
c[1] = textureLodOffset(materialParams_coc, uv, mip, ivec2(1, 1)).r;
|
||||
c[2] = textureLodOffset(materialParams_coc, uv, mip, ivec2(1, 0)).r;
|
||||
c[3] = textureLodOffset(materialParams_coc, uv, mip, ivec2(0, 0)).r;
|
||||
|
||||
float outCoc = downsampleCoC(c);
|
||||
vec4 w = downsampleCocWeights(c, outCoc, weightScale);
|
||||
|
||||
@@ -5,6 +5,10 @@ material {
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : level
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -26,8 +30,9 @@ fragment {
|
||||
// We use a rotated grid sub-sample as it's cheap and gives good results
|
||||
// See Scalable Ambient Obscurance by McGuire and al.
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
int level = materialParams.level;
|
||||
ivec2 icoord = ivec2(gl_FragCoord.xy);
|
||||
postProcess.depth = texelFetch(materialParams_depth,
|
||||
2 * icoord + ivec2(icoord.y & 1, icoord.x & 1), 0).r;
|
||||
2 * icoord + ivec2(icoord.y & 1, icoord.x & 1), level).r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ material {
|
||||
type : int,
|
||||
name : layer
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : level
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : uvscale
|
||||
@@ -32,6 +36,7 @@ fragment {
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec2 uv = gl_FragCoord.xy * materialParams.uvscale;
|
||||
postProcess.color = textureLod(materialParams_color,
|
||||
vec3(uv, materialParams.layer), 0.0);
|
||||
vec3(uv, materialParams.layer),
|
||||
float(materialParams.level));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.43.1"
|
||||
spec.version = "1.42.0"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.43.1/filament-v1.43.1-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.42.0/filament-v1.42.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
#define TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
#define TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 43;
|
||||
static constexpr size_t MATERIAL_VERSION = 42;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -35,7 +35,7 @@ for example, `filament-20181009-linux.tgz`.
|
||||
|
||||
Create a file, `main.cpp`, in the same directory with the following contents:
|
||||
|
||||
```c++
|
||||
```
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <iostream>
|
||||
@@ -85,7 +85,7 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory.
|
||||
|
||||
### Linux
|
||||
|
||||
```make
|
||||
```
|
||||
FILAMENT_LIBS=-lfilamat -lfilabridge -lshaders -lutils -lsmol-v
|
||||
CC=clang++
|
||||
|
||||
@@ -103,7 +103,7 @@ clean:
|
||||
|
||||
### macOS
|
||||
|
||||
```make
|
||||
```
|
||||
FILAMENT_LIBS=-lfilamat -lfilabridge -lshaders -lutils -lsmol-v
|
||||
CC=clang++
|
||||
|
||||
@@ -129,7 +129,7 @@ flags](https://docs.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-tim
|
||||
When building Filamat from source, the `USE_STATIC_CRT` CMake option can be
|
||||
used to change the run-time library version.
|
||||
|
||||
```make
|
||||
```
|
||||
FILAMENT_LIBS=lib/x86_64/mt/filamat.lib lib/x86_64/mt/filabridge.lib lib/x86_64/mt/shaders.lib \
|
||||
lib/x86_64/mt/utils.lib lib/x86_64/mt/smol-v.lib
|
||||
CC=clang-cl.exe
|
||||
@@ -164,7 +164,7 @@ and invoke `nmake` instead of `make`.
|
||||
For simplicity, this demo doesn't do anything useful with the built material package. To use the
|
||||
material with Filament, pass the material package's data into a Filament Material builder:
|
||||
|
||||
```c++
|
||||
```
|
||||
Package package = builder.build();
|
||||
filament::Material* myMaterial = Material::Builder()
|
||||
.package(package.getData(), package.getSize())
|
||||
@@ -186,7 +186,7 @@ In addition, `filamat_lite` only performs a simple text match to determine which
|
||||
`MaterialInputs` structure are set. The `material` input variable must also always be refered to by
|
||||
the name `material`.
|
||||
|
||||
```glsl
|
||||
```
|
||||
void anotherFunction(inout MaterialInputs m) {
|
||||
// Incorrect! The MaterialInputs is being referred to by the name "m".
|
||||
m.metallic = 0.0;
|
||||
|
||||
@@ -16,7 +16,6 @@ set(PUBLIC_HDRS
|
||||
include/gltfio/FilamentInstance.h
|
||||
include/gltfio/MaterialProvider.h
|
||||
include/gltfio/NodeManager.h
|
||||
include/gltfio/TrsTransformManager.h
|
||||
include/gltfio/ResourceLoader.h
|
||||
include/gltfio/TextureProvider.h
|
||||
include/gltfio/math.h
|
||||
@@ -36,12 +35,10 @@ set(SRCS
|
||||
src/FilamentAsset.cpp
|
||||
src/FilamentInstance.cpp
|
||||
src/FNodeManager.h
|
||||
src/FTrsTransformManager.h
|
||||
src/GltfEnums.h
|
||||
src/Ktx2Provider.cpp
|
||||
src/MaterialProvider.cpp
|
||||
src/NodeManager.cpp
|
||||
src/TrsTransformManager.cpp
|
||||
src/ResourceLoader.cpp
|
||||
src/StbProvider.cpp
|
||||
src/TangentsJob.cpp
|
||||
@@ -189,7 +186,8 @@ if (NOT WEBGL AND NOT ANDROID AND NOT IOS)
|
||||
# ==================================================================================================
|
||||
# Compiler flags
|
||||
# ==================================================================================================
|
||||
if (NOT MSVC)
|
||||
if (MSVC)
|
||||
else()
|
||||
target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register)
|
||||
endif()
|
||||
|
||||
@@ -227,9 +225,7 @@ if (TNT_DEV AND NOT WEBGL AND NOT ANDROID AND NOT IOS)
|
||||
add_dependencies(${TEST_TARGET} test_gltfio_files)
|
||||
|
||||
target_link_libraries(${TEST_TARGET} PRIVATE ${TARGET} filament filabridge gtest uberarchive)
|
||||
if (NOT MSVC)
|
||||
target_compile_options(${TEST_TARGET} PRIVATE -Wno-deprecated-register)
|
||||
endif()
|
||||
target_compile_options(${TEST_TARGET} PRIVATE -Wno-deprecated-register)
|
||||
set_target_properties(${TEST_TARGET} PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* 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 GLTFIO_TRSTRANSFORMMANAGER_H
|
||||
#define GLTFIO_TRSTRANSFORMMANAGER_H
|
||||
|
||||
#include <filament/FilamentAPI.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/EntityInstance.h>
|
||||
#include <math/quat.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/mat4.h>
|
||||
|
||||
using namespace filament::math;
|
||||
|
||||
namespace utils {
|
||||
class Entity;
|
||||
} // namespace utils
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
class FTrsTransformManager;
|
||||
|
||||
/**
|
||||
* TrsTransformManager is used to add entities with glTF-specific trs information.
|
||||
*
|
||||
* Trs information here just used for Animation, DON'T use for transform.
|
||||
*/
|
||||
class UTILS_PUBLIC TrsTransformManager {
|
||||
public:
|
||||
using Instance = utils::EntityInstance<TrsTransformManager>;
|
||||
using Entity = utils::Entity;
|
||||
|
||||
/**
|
||||
* Returns whether a particular Entity is associated with a component of this TrsTransformManager
|
||||
* @param e An Entity.
|
||||
* @return true if this Entity has a component associated with this manager.
|
||||
*/
|
||||
bool hasComponent(Entity e) const noexcept;
|
||||
|
||||
/**
|
||||
* Gets an Instance representing the trs transform component associated with the given Entity.
|
||||
* @param e An Entity.
|
||||
* @return An Instance object, which represents the trs transform component associated with the Entity e.
|
||||
* @note Use Instance::isValid() to make sure the component exists.
|
||||
* @see hasComponent()
|
||||
*/
|
||||
Instance getInstance(Entity e) const noexcept;
|
||||
|
||||
/**
|
||||
* Creates a trs transform component and associates it with the given entity.
|
||||
* @param entity An Entity to associate a trs transform component with.
|
||||
* @param translation The translation to initialize the trs transform component with.
|
||||
* @param rotation The rotation to initialize the trs transform component with.
|
||||
* @param scale The scale to initialize the trs transform component with.
|
||||
*
|
||||
* If this component already exists on the given entity, it is first destroyed as if
|
||||
* destroy(Entity e) was called.
|
||||
*
|
||||
* @see destroy()
|
||||
*/
|
||||
void create(Entity entity);
|
||||
void create(Entity entity, const float3& translation, const quatf& rotation,
|
||||
const float3& scale); //!< \overload
|
||||
|
||||
/**
|
||||
* Destroys this component from the given entity.
|
||||
* @param e An entity.
|
||||
*
|
||||
* @see create()
|
||||
*/
|
||||
void destroy(Entity e) noexcept;
|
||||
|
||||
void setTranslation(Instance ci, const float3& translation) noexcept;
|
||||
const float3& getTranslation(Instance ci) const noexcept;
|
||||
|
||||
void setRotation(Instance ci, const quatf& rotation) noexcept;
|
||||
const quatf& getRotation(Instance ci) const noexcept;
|
||||
|
||||
void setScale(Instance ci, const float3& scale) noexcept;
|
||||
const float3& getScale(Instance ci) const noexcept;
|
||||
|
||||
void setTrs(Instance ci, const float3& translation, const quatf& rotation,
|
||||
const float3& scale) noexcept;
|
||||
const mat4f getTransform(Instance ci) const noexcept;
|
||||
|
||||
protected:
|
||||
TrsTransformManager() noexcept = default;
|
||||
~TrsTransformManager() = default;
|
||||
|
||||
public:
|
||||
TrsTransformManager(TrsTransformManager const&) = delete;
|
||||
TrsTransformManager(TrsTransformManager&&) = delete;
|
||||
TrsTransformManager& operator=(TrsTransformManager const&) = delete;
|
||||
TrsTransformManager& operator=(TrsTransformManager&&) = delete;
|
||||
};
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
#endif // GLTFIO_TRSTRANSFORMMANAGER_H
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "FFilamentAsset.h"
|
||||
#include "FFilamentInstance.h"
|
||||
#include "FTrsTransformManager.h"
|
||||
#include "downcast.h"
|
||||
|
||||
#include <filament/VertexBuffer.h>
|
||||
@@ -75,7 +74,6 @@ struct AnimatorImpl {
|
||||
FFilamentInstance* instance = nullptr;
|
||||
RenderableManager* renderableManager;
|
||||
TransformManager* transformManager;
|
||||
TrsTransformManager* trsTransformManager;
|
||||
vector<float> weights;
|
||||
FixedCapacityVector<mat4f> crossFade;
|
||||
void addChannels(const FixedCapacityVector<Entity>& nodeMap, const cgltf_animation& srcAnim,
|
||||
@@ -192,7 +190,6 @@ Animator::Animator(FFilamentAsset const* asset, FFilamentInstance* instance) {
|
||||
mImpl->instance = instance;
|
||||
mImpl->renderableManager = &asset->mEngine->getRenderableManager();
|
||||
mImpl->transformManager = &asset->mEngine->getTransformManager();
|
||||
mImpl->trsTransformManager = asset->getTrsTransformManager();
|
||||
|
||||
const cgltf_data* srcAsset = asset->mSourceAsset->hierarchy;
|
||||
const cgltf_animation* srcAnims = srcAsset->animations;
|
||||
@@ -432,13 +429,20 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
size_t nextIndex) {
|
||||
const Sampler* sampler = channel.sourceData;
|
||||
const TimeValues& times = sampler->times;
|
||||
TrsTransformManager::Instance trsNode = trsTransformManager->getInstance(channel.targetEntity);
|
||||
TransformManager::Instance node = transformManager->getInstance(channel.targetEntity);
|
||||
|
||||
// Perform the interpolation. This is a simple but inefficient implementation; Filament
|
||||
// stores transforms as mat4's but glTF animation is based on TRS (translation rotation
|
||||
// scale).
|
||||
mat4f xform = transformManager->getTransform(node);
|
||||
float3 scale;
|
||||
quatf rotation;
|
||||
float3 translation;
|
||||
decomposeMatrix(xform, &translation, &rotation, &scale);
|
||||
|
||||
switch (channel.transformType) {
|
||||
|
||||
case Channel::SCALE: {
|
||||
float3 scale;
|
||||
const float3* srcVec3 = (const float3*) sampler->values.data();
|
||||
if (sampler->interpolation == Sampler::CUBIC) {
|
||||
float3 vert0 = srcVec3[prevIndex * 3 + 1];
|
||||
@@ -449,12 +453,10 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
} else {
|
||||
scale = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]);
|
||||
}
|
||||
trsTransformManager->setScale(trsNode, scale);
|
||||
break;
|
||||
}
|
||||
|
||||
case Channel::TRANSLATION: {
|
||||
float3 translation;
|
||||
const float3* srcVec3 = (const float3*) sampler->values.data();
|
||||
if (sampler->interpolation == Sampler::CUBIC) {
|
||||
float3 vert0 = srcVec3[prevIndex * 3 + 1];
|
||||
@@ -465,12 +467,10 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
} else {
|
||||
translation = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]);
|
||||
}
|
||||
trsTransformManager->setTranslation(trsNode, translation);
|
||||
break;
|
||||
}
|
||||
|
||||
case Channel::ROTATION: {
|
||||
quatf rotation;
|
||||
const quatf* srcQuat = (const quatf*) sampler->values.data();
|
||||
if (sampler->interpolation == Sampler::CUBIC) {
|
||||
quatf vert0 = srcQuat[prevIndex * 3 + 1];
|
||||
@@ -481,7 +481,6 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
} else {
|
||||
rotation = slerp(srcQuat[prevIndex], srcQuat[nextIndex], t);
|
||||
}
|
||||
trsTransformManager->setRotation(trsNode, rotation);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -520,7 +519,8 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
}
|
||||
}
|
||||
|
||||
transformManager->setTransform(node, trsTransformManager->getTransform(trsNode));
|
||||
xform = composeMatrix(translation, rotation, scale);
|
||||
transformManager->setTransform(node, xform);
|
||||
}
|
||||
|
||||
void AnimatorImpl::resetBoneMatrices(FFilamentInstance* instance) {
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include "FFilamentAsset.h"
|
||||
#include "FNodeManager.h"
|
||||
#include "FTrsTransformManager.h"
|
||||
#include "GltfEnums.h"
|
||||
|
||||
#include <filament/Box.h>
|
||||
@@ -296,7 +295,6 @@ public:
|
||||
MaterialProvider& mMaterials;
|
||||
Engine& mEngine;
|
||||
FNodeManager mNodeManager;
|
||||
FTrsTransformManager mTrsTransformManager;
|
||||
|
||||
// Transient state used only for the asset currently being loaded:
|
||||
FFilamentAsset* mAsset;
|
||||
@@ -402,8 +400,7 @@ void FAssetLoader::createRootAsset(const cgltf_data* srcAsset) {
|
||||
#endif
|
||||
|
||||
mDummyBufferObject = nullptr;
|
||||
mAsset = new FFilamentAsset(&mEngine, mNameManager, &mEntityManager, &mNodeManager,
|
||||
&mTrsTransformManager, srcAsset);
|
||||
mAsset = new FFilamentAsset(&mEngine, mNameManager, &mEntityManager, &mNodeManager, srcAsset);
|
||||
|
||||
// It is not an error for a glTF file to have zero scenes.
|
||||
mAsset->mScenes.clear();
|
||||
@@ -566,9 +563,7 @@ void FAssetLoader::recurseEntities(const cgltf_data* srcAsset, const cgltf_node*
|
||||
quatf* rotation = (quatf*) &node->rotation[0];
|
||||
float3* scale = (float3*) &node->scale[0];
|
||||
float3* translation = (float3*) &node->translation[0];
|
||||
mTrsTransformManager.create(entity, *translation, *rotation, *scale);
|
||||
localTransform = mTrsTransformManager.getTransform(
|
||||
mTrsTransformManager.getInstance(entity));
|
||||
localTransform = composeMatrix(*translation, *rotation, *scale);
|
||||
}
|
||||
|
||||
auto parentTransform = mTransformManager.getInstance(parent);
|
||||
@@ -1472,9 +1467,8 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse
|
||||
}
|
||||
}
|
||||
if (matkey.hasSheenRoughnessTexture) {
|
||||
bool sameTexture = shConfig.sheen_color_texture.texture == shConfig.sheen_roughness_texture.texture;
|
||||
mAsset->addTextureBinding(mi, "sheenRoughnessMap",
|
||||
shConfig.sheen_roughness_texture.texture, sameTexture ? sRGB : LINEAR);
|
||||
shConfig.sheen_roughness_texture.texture, LINEAR);
|
||||
if (matkey.hasTextureTransforms) {
|
||||
const cgltf_texture_transform& uvt = shConfig.sheen_roughness_texture.transform;
|
||||
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include <gltfio/FilamentAsset.h>
|
||||
#include <gltfio/NodeManager.h>
|
||||
#include <gltfio/TrsTransformManager.h>
|
||||
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/IndexBuffer.h>
|
||||
@@ -111,9 +110,9 @@ using MeshCache = utils::FixedCapacityVector<utils::FixedCapacityVector<Primitiv
|
||||
struct FFilamentAsset : public FilamentAsset {
|
||||
FFilamentAsset(Engine* engine, utils::NameComponentManager* names,
|
||||
utils::EntityManager* entityManager, NodeManager* nodeManager,
|
||||
TrsTransformManager* trsTransformManager, const cgltf_data* srcAsset) :
|
||||
const cgltf_data* srcAsset) :
|
||||
mEngine(engine), mNameManager(names), mEntityManager(entityManager),
|
||||
mNodeManager(nodeManager), mTrsTransformManager(trsTransformManager),
|
||||
mNodeManager(nodeManager),
|
||||
mSourceAsset(new SourceAsset {(cgltf_data*)srcAsset}),
|
||||
mTextures(srcAsset->textures_count),
|
||||
mMeshCache(srcAsset->meshes_count) {}
|
||||
@@ -196,10 +195,6 @@ struct FFilamentAsset : public FilamentAsset {
|
||||
return mEngine;
|
||||
}
|
||||
|
||||
TrsTransformManager* getTrsTransformManager() const noexcept {
|
||||
return mTrsTransformManager;
|
||||
}
|
||||
|
||||
void releaseSourceData() noexcept;
|
||||
|
||||
const void* getSourceAsset() const noexcept {
|
||||
@@ -247,7 +242,6 @@ struct FFilamentAsset : public FilamentAsset {
|
||||
utils::NameComponentManager* const mNameManager;
|
||||
utils::EntityManager* const mEntityManager;
|
||||
NodeManager* const mNodeManager;
|
||||
TrsTransformManager* const mTrsTransformManager;
|
||||
std::vector<utils::Entity> mEntities; // sorted such that renderables come first
|
||||
std::vector<utils::Entity> mLightEntities;
|
||||
std::vector<utils::Entity> mCameraEntities;
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* 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 GLTFIO_FTRSTRANSFORMMANAGER_H
|
||||
#define GLTFIO_FTRSTRANSFORMMANAGER_H
|
||||
|
||||
#include "downcast.h"
|
||||
#include "gltfio/math.h"
|
||||
#include "math/quat.h"
|
||||
#include "utils/debug.h"
|
||||
|
||||
#include <gltfio/TrsTransformManager.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/SingleInstanceComponentManager.h>
|
||||
#include <utils/Entity.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
class UTILS_PRIVATE FTrsTransformManager : public TrsTransformManager {
|
||||
public:
|
||||
using Instance = TrsTransformManager::Instance;
|
||||
|
||||
FTrsTransformManager() noexcept {}
|
||||
|
||||
~FTrsTransformManager() noexcept {
|
||||
assert_invariant(mManager.getComponentCount() == 0);
|
||||
}
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
bool hasComponent(utils::Entity e) const noexcept {
|
||||
return mManager.hasComponent(e);
|
||||
}
|
||||
|
||||
Instance getInstance(utils::Entity e) const noexcept {
|
||||
return Instance(mManager.getInstance(e));
|
||||
}
|
||||
|
||||
void create(utils::Entity entity) {
|
||||
create(entity, float3{}, quatf{}, float3{1});
|
||||
}
|
||||
|
||||
void create(utils::Entity entity, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) {
|
||||
if (UTILS_UNLIKELY(mManager.hasComponent(entity))) {
|
||||
destroy(entity);
|
||||
}
|
||||
UTILS_UNUSED_IN_RELEASE Instance ci = mManager.addComponent(entity);
|
||||
assert_invariant(ci);
|
||||
|
||||
if (ci) {
|
||||
setTrs(ci, translation, rotation, scale);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(utils::Entity e) noexcept {
|
||||
if (Instance ci = mManager.getInstance(e); ci) {
|
||||
mManager.removeComponent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void gc(utils::EntityManager& em) noexcept {
|
||||
mManager.gc(em);
|
||||
}
|
||||
|
||||
void setTranslation(Instance ci, const float3& translation) noexcept {
|
||||
assert_invariant(ci.isValid());
|
||||
mManager[ci].translation = translation;
|
||||
}
|
||||
|
||||
const float3& getTranslation(Instance ci) const noexcept {
|
||||
return mManager[ci].translation;
|
||||
}
|
||||
|
||||
void setRotation(Instance ci, const quatf& rotation) noexcept {
|
||||
assert_invariant(ci.isValid());
|
||||
mManager[ci].rotation = rotation;
|
||||
}
|
||||
|
||||
const quatf& getRotation(Instance ci) const noexcept {
|
||||
return mManager[ci].rotation;
|
||||
}
|
||||
|
||||
void setScale(Instance ci, const float3& scale) noexcept {
|
||||
assert_invariant(ci.isValid());
|
||||
mManager[ci].scale = scale;
|
||||
}
|
||||
|
||||
const float3& getScale(Instance ci) const noexcept {
|
||||
return mManager[ci].scale;
|
||||
}
|
||||
|
||||
void setTrs(Instance ci, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) noexcept {
|
||||
setTranslation(ci, translation);
|
||||
setRotation(ci, rotation);
|
||||
setScale(ci, scale);
|
||||
}
|
||||
|
||||
const mat4f getTransform(Instance ci) const noexcept {
|
||||
return composeMatrix(getTranslation(ci), getRotation(ci), getScale(ci));
|
||||
}
|
||||
|
||||
private:
|
||||
enum {
|
||||
TRANSLATION,
|
||||
ROTATION,
|
||||
SCALE,
|
||||
};
|
||||
|
||||
using Base = utils::SingleInstanceComponentManager<
|
||||
float3,
|
||||
quatf,
|
||||
float3>;
|
||||
|
||||
struct Sim : public Base {
|
||||
using Base::gc;
|
||||
using Base::swap;
|
||||
|
||||
typename Base::SoA& getSoA() { return mData; }
|
||||
|
||||
struct Proxy {
|
||||
UTILS_ALWAYS_INLINE
|
||||
Proxy(Base& sim, utils::EntityInstanceBase::Type i) noexcept :
|
||||
translation{ sim, i } { }
|
||||
|
||||
union {
|
||||
Field<TRANSLATION> translation;
|
||||
Field<ROTATION> rotation;
|
||||
Field<SCALE> scale;
|
||||
};
|
||||
};
|
||||
|
||||
UTILS_ALWAYS_INLINE Proxy operator[](Instance i) noexcept {
|
||||
return { *this, i };
|
||||
}
|
||||
UTILS_ALWAYS_INLINE const Proxy operator[](Instance i) const noexcept {
|
||||
return { const_cast<Sim&>(*this), i };
|
||||
}
|
||||
};
|
||||
|
||||
Sim mManager;
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(TrsTransformManager)
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
#endif // GLTFIO_FTRSTRANSFORMMANAGER_H
|
||||
@@ -60,11 +60,6 @@ FFilamentAsset::~FFilamentAsset() {
|
||||
|
||||
}
|
||||
|
||||
// Destroy gltfio trs transform components.
|
||||
for (auto entity : mEntities) {
|
||||
mTrsTransformManager->destroy(entity);
|
||||
}
|
||||
|
||||
// Destroy all renderable, light, transform, and camera components,
|
||||
// then destroy the actual entities. This includes instances.
|
||||
if (!mDetachedFilamentComponents) {
|
||||
|
||||
@@ -287,7 +287,7 @@ std::string shaderFromKey(const MaterialKey& config) {
|
||||
|
||||
// TODO: Provided by Filament, but this should really be provided/computed by gltfio
|
||||
// TODO: This scale is per renderable and should include the scale of the mesh node
|
||||
float scale = getObjectUserData();
|
||||
float scale = object_uniforms.userData;
|
||||
material.thickness = materialParams.volumeThicknessFactor * scale;
|
||||
)SHADER";
|
||||
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* 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 "FTrsTransformManager.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "downcast.h"
|
||||
#include "gltfio/TrsTransformManager.h"
|
||||
|
||||
using namespace utils;
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
using Instance = TrsTransformManager::Instance;
|
||||
|
||||
void FTrsTransformManager::terminate() noexcept {
|
||||
auto& manager = mManager;
|
||||
if (!manager.empty()) {
|
||||
#ifndef NDEBUG
|
||||
utils::slog.d << "cleaning up " << manager.getComponentCount()
|
||||
<< " leaked trs transform components" << utils::io::endl;
|
||||
#endif
|
||||
while (!manager.empty()) {
|
||||
Instance ci = manager.end() - 1;
|
||||
manager.removeComponent(manager.getEntity(ci));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TrsTransformManager::hasComponent(Entity e) const noexcept {
|
||||
return downcast(this)->hasComponent(e);
|
||||
}
|
||||
|
||||
Instance TrsTransformManager::getInstance(Entity e) const noexcept {
|
||||
return downcast(this)->getInstance(e);
|
||||
}
|
||||
|
||||
void TrsTransformManager::create(Entity entity) {
|
||||
downcast(this)->create(entity);
|
||||
}
|
||||
|
||||
void TrsTransformManager::create(Entity entity, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) {
|
||||
downcast(this)->create(entity, translation, rotation, scale);
|
||||
}
|
||||
|
||||
void TrsTransformManager::destroy(Entity e) noexcept {
|
||||
downcast(this)->destroy(e);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setTranslation(Instance ci, const float3& translation) noexcept {
|
||||
downcast(this)->setTranslation(ci, translation);
|
||||
}
|
||||
|
||||
const float3& TrsTransformManager::getTranslation(Instance ci) const noexcept {
|
||||
return downcast(this)->getTranslation(ci);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setRotation(Instance ci, const quatf& rotation) noexcept {
|
||||
downcast(this)->setRotation(ci, rotation);
|
||||
}
|
||||
|
||||
const quatf& TrsTransformManager::getRotation(Instance ci) const noexcept {
|
||||
return downcast(this)->getRotation(ci);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setScale(Instance ci, const float3& scale) noexcept {
|
||||
downcast(this)->setScale(ci, scale);
|
||||
}
|
||||
|
||||
const float3& TrsTransformManager::getScale(Instance ci) const noexcept {
|
||||
return downcast(this)->getScale(ci);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setTrs(Instance ci, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) noexcept {
|
||||
downcast(this)->setTrs(ci, translation, rotation, scale);
|
||||
}
|
||||
|
||||
const mat4f TrsTransformManager::getTransform(Instance ci) const noexcept {
|
||||
return downcast(this)->getTransform(ci);
|
||||
}
|
||||
|
||||
} // namespace filament::gltfio
|
||||
@@ -25,7 +25,10 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace filament::math::details {
|
||||
namespace filament {
|
||||
namespace math {
|
||||
namespace details {
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* No user serviceable parts here.
|
||||
@@ -46,7 +49,7 @@ namespace filament::math::details {
|
||||
template<template<typename T> class QUATERNION, typename T>
|
||||
class TQuatProductOperators {
|
||||
public:
|
||||
/* compound assignment from another quaternion of the same size but different
|
||||
/* compound assignment from a another quaternion of the same size but different
|
||||
* element type.
|
||||
*/
|
||||
template<typename OTHER>
|
||||
@@ -82,7 +85,7 @@ public:
|
||||
* (the first one, BASE<T> being known).
|
||||
*/
|
||||
|
||||
/* The operators below handle operation between quaternions of the same size
|
||||
/* The operators below handle operation between quaternion of the same size
|
||||
* but of a different element type.
|
||||
*/
|
||||
template<typename RT>
|
||||
@@ -124,19 +127,19 @@ public:
|
||||
*/
|
||||
friend inline
|
||||
constexpr QUATERNION<T> MATH_PURE operator*(QUATERNION<T> q, T scalar) {
|
||||
// don't pass q by reference because we need a copy anyway
|
||||
// don't pass q by reference because we need a copy anyways
|
||||
return q *= scalar;
|
||||
}
|
||||
|
||||
friend inline
|
||||
constexpr QUATERNION<T> MATH_PURE operator*(T scalar, QUATERNION<T> q) {
|
||||
// don't pass q by reference because we need a copy anyway
|
||||
// don't pass q by reference because we need a copy anyways
|
||||
return q *= scalar;
|
||||
}
|
||||
|
||||
friend inline
|
||||
constexpr QUATERNION<T> MATH_PURE operator/(QUATERNION<T> q, T scalar) {
|
||||
// don't pass q by reference because we need a copy anyway
|
||||
// don't pass q by reference because we need a copy anyways
|
||||
return q /= scalar;
|
||||
}
|
||||
};
|
||||
@@ -280,6 +283,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace filament::math::details
|
||||
// -------------------------------------------------------------------------------------
|
||||
} // namespace details
|
||||
} // namespace math
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_MATH_TQUATHELPERS_H
|
||||
|
||||
@@ -443,37 +443,6 @@ private:
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
friend inline
|
||||
VECTOR<T> MATH_PURE fmod(VECTOR<T> const& x, VECTOR<U> const& y) {
|
||||
VECTOR<T> r;
|
||||
for (size_t i = 0; i < r.size(); i++) {
|
||||
r[i] = std::fmod(x[i], y[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
friend inline
|
||||
VECTOR<T> MATH_PURE remainder(VECTOR<T> const& x, VECTOR<U> const& y) {
|
||||
VECTOR<T> r;
|
||||
for (size_t i = 0; i < r.size(); i++) {
|
||||
r[i] = std::remainder(x[i], y[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
friend inline
|
||||
VECTOR<T> MATH_PURE remquo(VECTOR<T> const& x, VECTOR<U> const& y,
|
||||
VECTOR<int>* q) {
|
||||
VECTOR<T> r;
|
||||
for (size_t i = 0; i < r.size(); i++) {
|
||||
r[i] = std::remquo(x[i], y[i], &((*q)[i]));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
friend inline VECTOR<T> MATH_PURE inversesqrt(VECTOR<T> v) {
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
v[i] = T(1) / std::sqrt(v[i]);
|
||||
|
||||
@@ -160,7 +160,7 @@ constexpr inline half makeHalf(uint16_t bits) noexcept {
|
||||
|
||||
#endif // __ARM_NEON
|
||||
|
||||
inline constexpr half operator""_h(long double v) {
|
||||
inline constexpr half operator "" _h(long double v) {
|
||||
return half( static_cast<float>(v) );
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::math {
|
||||
namespace filament {
|
||||
namespace math {
|
||||
namespace details {
|
||||
|
||||
template<typename T> class TVec2;
|
||||
@@ -44,8 +45,6 @@ template<typename T> class TMat22;
|
||||
template<typename T> class TMat33;
|
||||
template<typename T> class TMat44;
|
||||
|
||||
template<typename T> class TQuaternion;
|
||||
|
||||
} // namespace details
|
||||
|
||||
using double2 = details::TVec2<double>;
|
||||
@@ -87,10 +86,8 @@ using mat3f = details::TMat33<float>;
|
||||
using mat4 = details::TMat44<double>;
|
||||
using mat4f = details::TMat44<float>;
|
||||
|
||||
using quat = details::TQuaternion<double>;
|
||||
using quatf = details::TQuaternion<float>;
|
||||
|
||||
} // namespace filament::math
|
||||
} // namespace math
|
||||
} // namespace filament
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace filament::math {
|
||||
namespace filament {
|
||||
namespace math {
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
namespace details {
|
||||
|
||||
template<typename T>
|
||||
@@ -86,15 +89,15 @@ public:
|
||||
|
||||
// constructors
|
||||
|
||||
// Leaves object uninitialized. Use with caution.
|
||||
// leaves object uninitialized. use with caution.
|
||||
explicit constexpr TQuaternion(no_init) {}
|
||||
|
||||
// default constructor. sets all values to zero.
|
||||
constexpr TQuaternion() : x(0), y(0), z(0), w(0) {}
|
||||
|
||||
// Handles implicit conversion to a quat. Must not be explicit.
|
||||
// handles implicit conversion to a quat. must not be explicit.
|
||||
template<typename A, typename = enable_if_arithmetic_t<A>>
|
||||
constexpr TQuaternion(A w) : x(0), y(0), z(0), w(w) {} // NOLINT(google-explicit-constructor)
|
||||
constexpr TQuaternion(A w) : x(0), y(0), z(0), w(w) {}
|
||||
|
||||
// initialize from 4 values to w + xi + yj + zk
|
||||
template<typename A, typename B, typename C, typename D,
|
||||
@@ -172,29 +175,31 @@ typedef details::TQuaternion<float> quatf;
|
||||
typedef details::TQuaternion<half> quath;
|
||||
|
||||
constexpr inline quat operator "" _i(long double v) {
|
||||
return { 0.0, double(v), 0.0, 0.0 };
|
||||
return quat(0.0, double(v), 0.0, 0.0);
|
||||
}
|
||||
|
||||
constexpr inline quat operator "" _j(long double v) {
|
||||
return { 0.0, 0.0, double(v), 0.0 };
|
||||
return quat(0.0, 0.0, double(v), 0.0);
|
||||
}
|
||||
|
||||
constexpr inline quat operator "" _k(long double v) {
|
||||
return { 0.0, 0.0, 0.0, double(v) };
|
||||
return quat(0.0, 0.0, 0.0, double(v));
|
||||
}
|
||||
|
||||
constexpr inline quat operator "" _i(unsigned long long v) {
|
||||
return { 0.0, double(v), 0.0, 0.0 };
|
||||
return quat(0.0, double(v), 0.0, 0.0);
|
||||
}
|
||||
|
||||
constexpr inline quat operator "" _j(unsigned long long v) {
|
||||
return { 0.0, 0.0, double(v), 0.0 };
|
||||
return quat(0.0, 0.0, double(v), 0.0);
|
||||
}
|
||||
|
||||
constexpr inline quat operator "" _k(unsigned long long v) {
|
||||
return { 0.0, 0.0, 0.0, double(v) };
|
||||
return quat(0.0, 0.0, 0.0, double(v));
|
||||
}
|
||||
|
||||
} // namespace filament::math
|
||||
// ----------------------------------------------------------------------------------------
|
||||
} // namespace math
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_MATH_QUAT_H
|
||||
|
||||
@@ -36,6 +36,13 @@ public:
|
||||
* @see CallStack::capture()
|
||||
*/
|
||||
CallStack() = default;
|
||||
template <unsigned long N>
|
||||
explicit CallStack(const std::array<intptr_t, N>& symbols)
|
||||
: m_frame_count(N) {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
m_stack[i] = symbols[i];
|
||||
}
|
||||
}
|
||||
CallStack(const CallStack&) = default;
|
||||
~CallStack() = default;
|
||||
|
||||
@@ -114,12 +121,10 @@ private:
|
||||
|
||||
static constexpr size_t NUM_FRAMES = 20;
|
||||
|
||||
struct StackFrameInfo {
|
||||
intptr_t pc;
|
||||
};
|
||||
using StackFrameInfo = intptr_t;
|
||||
|
||||
size_t m_frame_count = 0;
|
||||
StackFrameInfo m_stack[NUM_FRAMES];
|
||||
StackFrameInfo m_stack[NUM_FRAMES] = {0};
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
@@ -70,7 +70,7 @@ intptr_t CallStack::operator[](size_t index) const {
|
||||
#endif
|
||||
std::abort();
|
||||
}
|
||||
return m_stack[index].pc;
|
||||
return m_stack[index];
|
||||
}
|
||||
|
||||
size_t CallStack::getFrameCount() const noexcept {
|
||||
@@ -91,7 +91,7 @@ void CallStack::update_gcc(size_t ignore) noexcept {
|
||||
size -= ignore;
|
||||
#endif
|
||||
for (ssize_t i = 0; i < size; i++) {
|
||||
m_stack[i].pc = intptr_t(array[ignore + i]);
|
||||
m_stack[i] = intptr_t(array[ignore + i]);
|
||||
}
|
||||
size--; // the last one seems to always be 0x0
|
||||
|
||||
|
||||
@@ -362,13 +362,19 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
|
||||
static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
|
||||
LightManager::ShadowOptions* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int const size = tokens[i++].size;
|
||||
int size = tokens[i++].size;
|
||||
math::float3 splitsVector;
|
||||
for (int j = 0; j < size; ++j) {
|
||||
const jsmntok_t tok = tokens[i];
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "mapSize") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->mapSize);
|
||||
} else if (compare(tok, jsonChunk, "stable") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->stable);
|
||||
} else if (compare(tok, jsonChunk, "lispsm") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lispsm);
|
||||
} else if (compare(tok, jsonChunk, "screenSpaceContactShadows") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->screenSpaceContactShadows);
|
||||
} else if (compare(tok, jsonChunk, "shadowCascades") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->shadowCascades);
|
||||
} else if (compare(tok, jsonChunk, "cascadeSplitPositions") == 0) {
|
||||
@@ -376,27 +382,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk,
|
||||
out->cascadeSplitPositions[0] = splitsVector[0];
|
||||
out->cascadeSplitPositions[1] = splitsVector[1];
|
||||
out->cascadeSplitPositions[2] = splitsVector[2];
|
||||
// TODO: constantBias
|
||||
// TODO: normalBias
|
||||
// TODO: shadowFar
|
||||
// TODO: shadowNearHint
|
||||
// TODO: shadowFarHint
|
||||
} else if (compare(tok, jsonChunk, "stable") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->stable);
|
||||
} else if (compare(tok, jsonChunk, "lispsm") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lispsm);
|
||||
// TODO: polygonOffsetConstant
|
||||
// TODO: polygonOffsetSlope
|
||||
} else if (compare(tok, jsonChunk, "screenSpaceContactShadows") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->screenSpaceContactShadows);
|
||||
// TODO: stepCount
|
||||
// TODO: maxShadowDistance
|
||||
} else if (compare(tok, jsonChunk, "vsm") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->vsm);
|
||||
} else if (compare(tok, jsonChunk, "shadowBulbRadius") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->shadowBulbRadius);
|
||||
} else if (compare(tok, jsonChunk, "transform") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->transform.xyzw);
|
||||
} else {
|
||||
slog.w << "Invalid shadow options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
@@ -578,9 +565,11 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight*
|
||||
ibl->setRotation(math::mat3f::rotation(settings.iblRotation, math::float3 { 0, 1, 0 }));
|
||||
}
|
||||
for (size_t i = 0; i < sceneLightCount; i++) {
|
||||
auto const li = lm->getInstance(sceneLights[i]);
|
||||
lm->setShadowCaster(li, settings.enableShadows);
|
||||
lm->setShadowOptions(li, settings.shadowOptions);
|
||||
light = lm->getInstance(sceneLights[i]);
|
||||
if (lm->isSpotLight(light)) {
|
||||
lm->setShadowCaster(light, settings.enableShadows);
|
||||
}
|
||||
lm->setShadowOptions(light, settings.shadowOptions);
|
||||
}
|
||||
view->setSoftShadowOptions(settings.softShadowOptions);
|
||||
}
|
||||
@@ -723,20 +712,18 @@ static std::ostream& operator<<(std::ostream& out, const ColorGradingSettings& i
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, const LightManager::ShadowOptions& in) {
|
||||
const float* splits = in.cascadeSplitPositions;
|
||||
math::float3 const splitsVector = { splits[0], splits[1], splits[2] };
|
||||
math::float3 splitsVector = { splits[0], splits[1], splits[2] };
|
||||
return out << "{\n"
|
||||
<< "\"vsm\": {\n"
|
||||
<< "\"elvsm\": " << to_string(in.vsm.elvsm) << ",\n"
|
||||
<< "\"blurWidth\": " << in.vsm.blurWidth << "\n"
|
||||
<< "},\n"
|
||||
<< "\"mapSize\": " << in.mapSize << ",\n"
|
||||
<< "\"shadowCascades\": " << int(in.shadowCascades) << ",\n"
|
||||
<< "\"cascadeSplitPositions\": " << (splitsVector) << "\n"
|
||||
<< "\"stable\": " << to_string(in.stable) << ",\n"
|
||||
<< "\"lispsm\": " << to_string(in.lispsm) << ",\n"
|
||||
<< "\"screenSpaceContactShadows\": " << to_string(in.screenSpaceContactShadows) << ",\n"
|
||||
<< "\"shadowBulbRadius\": " << in.shadowBulbRadius << ",\n"
|
||||
<< "\"transform\": " << in.transform.xyzw << ",\n"
|
||||
<< "\"shadowCascades\": " << int(in.shadowCascades) << ",\n"
|
||||
<< "\"cascadeSplitPositions\": " << (splitsVector) << "\n"
|
||||
<< "}";
|
||||
}
|
||||
|
||||
|
||||
@@ -249,8 +249,6 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, BloomOptions* o
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->enabled);
|
||||
} else if (compare(tok, jsonChunk, "highlight") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->highlight);
|
||||
} else if (compare(tok, jsonChunk, "quality") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->quality);
|
||||
} else if (compare(tok, jsonChunk, "lensFlare") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lensFlare);
|
||||
} else if (compare(tok, jsonChunk, "starburst") == 0) {
|
||||
@@ -293,7 +291,6 @@ std::ostream& operator<<(std::ostream& out, const BloomOptions& in) {
|
||||
<< "\"threshold\": " << to_string(in.threshold) << ",\n"
|
||||
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
|
||||
<< "\"highlight\": " << (in.highlight) << ",\n"
|
||||
<< "\"quality\": " << (in.quality) << ",\n"
|
||||
<< "\"lensFlare\": " << to_string(in.lensFlare) << ",\n"
|
||||
<< "\"starburst\": " << to_string(in.starburst) << ",\n"
|
||||
<< "\"chromaticAberration\": " << (in.chromaticAberration) << ",\n"
|
||||
|
||||
@@ -777,10 +777,6 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::SliderInt("Levels", &levels, 3, 11);
|
||||
mSettings.view.bloom.levels = levels;
|
||||
|
||||
int quality = (int) mSettings.view.bloom.quality;
|
||||
ImGui::SliderInt("Bloom Quality", &quality, 0, 3);
|
||||
mSettings.view.bloom.quality = (View::QualityLevel) quality;
|
||||
|
||||
ImGui::Checkbox("Lens Flare", &mSettings.view.bloom.lensFlare);
|
||||
}
|
||||
|
||||
@@ -857,14 +853,6 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::SliderFloat("Halo falloff", &light.sunlightHaloFalloff, 4.0f, 1024.0f);
|
||||
ImGui::SliderFloat("Sun radius", &light.sunlightAngularRadius, 0.1f, 10.0f);
|
||||
ImGuiExt::DirectionWidget("Sun direction", light.sunlightDirection.v);
|
||||
|
||||
float3 shadowDirection = light.shadowOptions.transform * light.sunlightDirection;
|
||||
ImGuiExt::DirectionWidget("Shadow direction", shadowDirection.v);
|
||||
light.shadowOptions.transform = normalize(quatf{
|
||||
cross(light.sunlightDirection, shadowDirection),
|
||||
sqrt(length2(light.sunlightDirection) * length2(shadowDirection))
|
||||
+ dot(light.sunlightDirection, shadowDirection)
|
||||
});
|
||||
}
|
||||
if (ImGui::CollapsingHeader("All lights")) {
|
||||
ImGui::Checkbox("Enable shadows", &light.enableShadows);
|
||||
|
||||
@@ -101,7 +101,6 @@ struct App {
|
||||
|
||||
bool actualSize = false;
|
||||
bool originIsFarAway = false;
|
||||
float originDistance = 6378137; // Earth's radius in [m]
|
||||
|
||||
struct Scene {
|
||||
Entity groundPlane;
|
||||
@@ -760,7 +759,6 @@ int main(int argc, char** argv) {
|
||||
ImGui::Checkbox("Disable buffer padding", debug.getPropertyAddress<bool>("d.renderer.disable_buffer_padding"));
|
||||
ImGui::Checkbox("Camera at origin", debug.getPropertyAddress<bool>("d.view.camera_at_origin"));
|
||||
ImGui::Checkbox("Far Origin", &app.originIsFarAway);
|
||||
ImGui::SliderFloat("Origin", &app.originDistance, 0, 10000000);
|
||||
auto dataSource = debug.getDataSource("d.view.frame_info");
|
||||
if (dataSource.data) {
|
||||
ImGuiExt::PlotLinesSeries("FrameInfo", 6,
|
||||
@@ -958,7 +956,7 @@ int main(int argc, char** argv) {
|
||||
tcm.setParent(tcm.getInstance(camera.getEntity()), root);
|
||||
tcm.setParent(tcm.getInstance(app.asset->getRoot()), root);
|
||||
tcm.setParent(tcm.getInstance(view->getFogEntity()), root);
|
||||
tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? app.originDistance : 0.0f }));
|
||||
tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? 1e6f : 0.0f }));
|
||||
|
||||
// Check if color grading has changed.
|
||||
ColorGradingSettings& options = app.viewer->getSettings().view.colorGrading;
|
||||
|
||||
@@ -109,7 +109,7 @@ highp vec4 getSpotLightSpacePosition(int index, highp vec3 dir, highp float zLig
|
||||
// for spotlights, the bias depends on z
|
||||
float bias = shadowUniforms.shadows[index].normalBias * zLight;
|
||||
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldGeometricNormalVector(),
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
||||
dir, bias, lightFromWorldMatrix);
|
||||
}
|
||||
#endif
|
||||
@@ -141,7 +141,7 @@ highp vec4 getCascadeLightSpacePosition(int cascade) {
|
||||
return vertex_lightSpacePosition;
|
||||
}
|
||||
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldGeometricNormalVector(),
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
||||
frameUniforms.lightDirection,
|
||||
shadowUniforms.shadows[cascade].normalBias,
|
||||
shadowUniforms.shadows[cascade].lightFromWorldMatrix);
|
||||
|
||||
@@ -45,8 +45,8 @@ end
|
||||
|
||||
Then run:
|
||||
|
||||
```shell
|
||||
pod install
|
||||
```
|
||||
$ pod install
|
||||
```
|
||||
|
||||
Close the project and then re-open the newly created HelloCocoaPods.xcworkspace file.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user