Compare commits

..
Author SHA1 Message Date
Benjamin Doherty 031cd302dd Capture command callstacks for debugging 2023-10-30 14:42:24 -07:00
201 changed files with 2034 additions and 5451 deletions
-11
View File
@@ -1,11 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.{c,cpp,h,inc,kt,java,js,md}]
indent_style = space
indent_size = 4
max_line_length = 100
-30
View File
@@ -1,30 +0,0 @@
name: CocoaPods Deploy
# This must be run after the iOS release job has finished, and the iOS release
# asset has been uploaded to Github.
on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag to deploy (e.g., v1.42.2)'
required: true
default: 'v1.42.2'
jobs:
cocoapods-deploy:
name: cocoapods-deploy
runs-on: macos-latest
steps:
- name: Check out iOS/CocoaPods directory
uses: Bhacaz/checkout-files@49fc3050859046bf4f4873678d46099985640e89
with:
files: ios/CocoaPods
token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.event.inputs.release_tag }}
- name: Move podspec to root
run: mv ios/CocoaPods/*.podspec .
- name: Install CocoaPods
run: gem install cocoapods
- uses: michaelhenry/deploy-to-cocoapods-github-action@745686ab065f90596e0d5cfcf97bb2416d94262e
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
-33
View File
@@ -1,33 +0,0 @@
name: Npm Deploy
on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag to deploy (e.g., v1.42.2)'
required: true
default: 'v1.42.2'
jobs:
npm-deploy:
name: npm-deploy
runs-on: macos-latest
steps:
- uses: actions/checkout@v3.3.0
with:
ref: ${{ github.event.inputs.release_tag }}
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v3
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh release
- name: Deploy to npm
run: |
cd out/cmake-webgl-release/web/filament-js
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+2 -1
View File
@@ -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
View File
@@ -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
```
-8
View File
@@ -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
View File
@@ -27,7 +27,7 @@ again.
## Code Style
See [CODE_STYLE.md](/CODE_STYLE.md)
See [CodeStyle.md](/CODE_STYLE.md)
## Code reviews
+6
View File
@@ -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`
+3 -3
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.44.0'
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.44.0'
```
pod 'Filament', '~> 1.42.0'
```
### Snapshots
-31
View File
@@ -7,39 +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.44.0
- engine: add support for skinning with more than four bones per vertex.
- engine: remove `BloomOptions::anamorphism` which wasn't working well in most cases [**API CHANGE**]
- engine: new API to return a Material's supported variants, C++ only (b/297456590)
- build: fix emscripten-1.3.46 build
- engine: materials built for feature level 0 can now also be loaded in higher feature levels [⚠️
**New Material Version**]
## v1.43.1
## v1.43.0
- 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);
}
@@ -282,7 +282,7 @@ Java_com_google_android_filament_View_nSetSSCTOptions(JNIEnv *, jclass, jlong na
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass,
jlong nativeView, jlong nativeTexture,
jfloat dirtStrength, jfloat strength, jint resolution, jint levels,
jfloat dirtStrength, jfloat strength, jint resolution, jfloat anamorphism, jint levels,
jint blendMode, jboolean threshold, jboolean enabled, jfloat highlight,
jboolean lensFlare, jboolean starburst, jfloat chromaticAberration, jint ghostCount,
jfloat ghostSpacing, jfloat ghostThreshold, jfloat haloThickness, jfloat haloRadius,
@@ -294,6 +294,7 @@ Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass,
.dirtStrength = dirtStrength,
.strength = strength,
.resolution = (uint32_t)resolution,
.anamorphism = anamorphism,
.levels = (uint8_t)levels,
.blendMode = (View::BloomOptions::BlendMode)blendMode,
.threshold = (bool)threshold,
@@ -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);
@@ -904,7 +904,7 @@ public class View {
mBloomOptions = options;
nSetBloomOptions(getNativeObject(), options.dirt != null ? options.dirt.getNativeObject() : 0,
options.dirtStrength, options.strength, options.resolution,
options.levels, options.blendMode.ordinal(),
options.anamorphism, options.levels, options.blendMode.ordinal(),
options.threshold, options.enabled, options.highlight,
options.lensFlare, options.starburst, options.chromaticAberration,
options.ghostCount, options.ghostSpacing, options.ghostThreshold,
@@ -1216,7 +1216,7 @@ public class View {
private static native int nGetAmbientOcclusion(long nativeView);
private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, float bilateralThreshold, int quality, int lowPassFilter, int upsampling, boolean enabled, boolean bentNormals, float minHorizonAngleRad);
private static native void nSetSSCTOptions(long nativeView, float ssctLightConeRad, float ssctStartTraceDistance, float ssctContactDistanceMax, float ssctIntensity, float v, float v1, float v2, float ssctDepthBias, float ssctDepthSlopeBias, int ssctSampleCount, int ssctRayCount, boolean ssctEnabled);
private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, int levels, int blendMode, boolean threshold, boolean enabled, float highlight,
private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled, float highlight,
boolean lensFlare, boolean starburst, float chromaticAberration, int ghostCount, float ghostSpacing, float ghostThreshold, float haloThickness, float haloRadius, float haloThreshold);
private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float cutOffDistance, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, long skyColorNativeObject, boolean enabled);
private static native void nSetBlendMode(long nativeView, int blendMode);
@@ -1353,6 +1353,8 @@ public class View {
* blendMode: Whether the bloom effect is purely additive (false) or mixed with the original
* image (true).
*
* anamorphism: Bloom's aspect ratio (x/y), for artistic purposes.
*
* threshold: When enabled, a threshold at 1.0 is applied on the source image, this is
* useful for artistic reasons and is usually needed when a dirt texture is used.
*
@@ -1391,6 +1393,10 @@ public class View {
* resolution of vertical axis (2^levels to 2048)
*/
public int resolution = 384;
/**
* bloom x/y aspect-ratio (1/32 to 32)
*/
public float anamorphism = 1.0f;
/**
* number of blur levels (1 to 11)
*/
@@ -1412,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
*/
@@ -17,8 +17,6 @@
// Operators +, *, / based on http://half.sourceforge.net/ by Christian Rau
// and licensed under MIT
@file:Suppress("NOTHING_TO_INLINE")
package com.google.android.filament.utils
import com.google.android.filament.utils.Half.Companion.POSITIVE_INFINITY
-3
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.44.0
VERSION_NAME=1.42.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+8 -18
View File
@@ -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.
@@ -160,7 +160,11 @@ class MainActivity : Activity() {
}
private fun loadMaterial() {
readUncompressedAsset("materials/baked_color.filamat").let {
var name = "materials/baked_color.filamat"
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
name = "materials/baked_color_es2.filamat"
}
readUncompressedAsset(name).let {
material = Material.Builder().payload(it, it.remaining()).build(engine)
}
}
@@ -17,8 +17,7 @@ material {
],
// This material disables all lighting
shadingModel : unlit,
featureLevel : 0
shadingModel : unlit
}
fragment {
@@ -0,0 +1,33 @@
// Simple unlit material that uses the colors associated with each vertex.
//
// This source material must be compiled to a binary material using the matc tool.
// The command used to compile this material is:
// matc -p mobile -a opengl -o app/src/main/assets/baked_color.filamat app/src/materials/baked_color.mat
//
// See build.gradle for an example of how to compile materials automatically
// Please refer to the documentation for more information about matc and the materials system.
material {
name : baked_color,
// Lists the required vertex attributes
// Here we only need a color (RGBA)
requires : [
color
],
// This material disables all lighting
shadingModel : unlit,
featureLevel : 0
}
fragment {
void material(inout MaterialInputs material) {
// You must always call the prepareMaterial() function
prepareMaterial(material);
// We set the material's color to the color interpolated from
// the model's vertices
material.baseColor = getColor();
}
}
+32 -54
View File
@@ -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,19 +754,6 @@ function run_tests {
fi
}
function check_debug_release_build {
if [[ "${ISSUE_DEBUG_BUILD}" == "true" || \
"${ISSUE_RELEASE_BUILD}" == "true" || \
"${ISSUE_CLEAN}" == "true" || \
"${ISSUE_WEB_DOCS}" == "true" ]]; then
"$@";
else
echo "You must declare a debug or release target for $@ builds."
echo ""
exit 1
fi
}
# Beginning of the script
pushd "$(dirname "$0")" > /dev/null
@@ -772,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
@@ -808,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
;;
@@ -827,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
;;
@@ -845,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
;;
@@ -864,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
;;
@@ -933,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}"
@@ -953,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
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+14 -13
View File
@@ -22,26 +22,27 @@ converting <a href="https://github.com/google/filament/blob/main/assets/models/m
<p>Next, let&#x27;s create mipmapped KTX files using filament&#x27;s <code>mipgen</code> tool. We&#x27;ll create compressed and
non-compressed variants for each texture, since not all platforms support the same compression
formats. First copy over the PNG files from the <a href="https://github.com/google/filament/blob/main/assets/models/monkey">monkey folder</a>, then do:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic"># Create mipmaps for base color</span>
mipgen albedo.png albedo.ktx2
mipgen --compression<span style="color: #666666">=</span>uastc albedo.png albedo.ktx2
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic"># Create mipmaps for base color and two compressed variants.</span>
mipgen albedo.png albedo.ktx
mipgen --compression<span style="color: #666666">=</span>astc_fast_ldr_4x4 albedo.png albedo_astc.ktx
mipgen --compression<span style="color: #666666">=</span>s3tc_rgb_dxt1 albedo.png albedo_s3tc_srgb.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the normal map and a compressed variant.</span>
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear normal.png normal.ktx
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear --compression<span style="color: #666666">=</span>uastc_normals <span style="color: #AA5D1F; font-weight: bold">\</span>
normal.png normal.ktx2
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear --compression<span style="color: #666666">=</span>etc_rgb8_normalxyz_40 <span style="color: #AA5D1F; font-weight: bold">\</span>
normal.png normal_etc.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component roughness map and a compressed variant.</span>
mipgen --grayscale roughness.png roughness.ktx
mipgen --grayscale --compression<span style="color: #666666">=</span>uastc roughness.png roughness.ktx2
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 roughness.png roughness_etc.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component metallic map and a compressed variant.</span>
mipgen --grayscale metallic.png metallic.ktx
mipgen --grayscale --compression<span style="color: #666666">=</span>uastc metallic.png metallic.ktx2
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 metallic.png metallic_etc.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component occlusion map and a compressed variant.</span>
mipgen --grayscale ao.png ao.ktx
mipgen --grayscale --compression<span style="color: #666666">=</span>uastc ao.png ao.ktx2
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 ao.png ao_etc.ktx
</pre></div>
<p>For more information on mipgen&#x27;s arguments and supported formats, do <code>mipgen --help</code>.</p>
@@ -206,11 +207,11 @@ when the assets have finished downloading.</p>
recreate the skybox using a higher-resolution texture. As a last step we unhide the renderable that
was created in the app constructor.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>Filament.fetch([sky_large_url,<span style="color: #bbbbbb"> </span>albedo_url,<span style="color: #bbbbbb"> </span>roughness_url,<span style="color: #bbbbbb"> </span>metallic_url,<span style="color: #bbbbbb"> </span>normal_url,<span style="color: #bbbbbb"> </span>ao_url],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(albedo_url,<span style="color: #bbbbbb"> </span>{srgb<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(roughness_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(metallic_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(normal_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx2(ao_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(albedo_url,<span style="color: #bbbbbb"> </span>{srgb<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(roughness_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(metallic_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(normal_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(ao_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sampler<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.TextureSampler(<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>Filament.MinFilter.LINEAR_MIPMAP_LINEAR,<span style="color: #bbbbbb"></span>
+5 -5
View File
@@ -33,13 +33,13 @@ class App {
});
Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url],
() => {
const albedo = this.engine.createTextureFromKtx2(albedo_url, {
const albedo = this.engine.createTextureFromKtx1(albedo_url, {
srgb: true
});
const roughness = this.engine.createTextureFromKtx2(roughness_url);
const metallic = this.engine.createTextureFromKtx2(metallic_url);
const normal = this.engine.createTextureFromKtx2(normal_url);
const ao = this.engine.createTextureFromKtx2(ao_url);
const roughness = this.engine.createTextureFromKtx1(roughness_url);
const metallic = this.engine.createTextureFromKtx1(metallic_url);
const normal = this.engine.createTextureFromKtx1(normal_url);
const ao = this.engine.createTextureFromKtx1(ao_url);
const sampler = new Filament.TextureSampler(Filament.MinFilter.LINEAR_MIPMAP_LINEAR,
Filament.MagFilter.LINEAR, Filament.WrapMode.CLAMP_TO_EDGE);
this.matinstance.setTextureParameter('albedo', albedo, sampler);
+1 -3
View File
@@ -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
@@ -557,7 +555,7 @@ if (MSVC)
set(FILAMENT_WARNINGS /W3)
else()
set(FILAMENT_WARNINGS
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
-Wall -Wextra -Wno-unused-parameter
-Wextra-semi -Wnewline-eof -Wdeprecated -Wundef
-Wgnu-conditional-omitted-operand
-Wweak-vtables -Wnon-virtual-dtor -Wclass-varargs -Wimplicit-fallthrough
+8 -8
View File
@@ -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.
+3 -4
View File
@@ -27,7 +27,6 @@ set(SRCS
src/BackendUtils.cpp
src/BlobCacheKey.cpp
src/Callable.cpp
src/CallbackManager.cpp
src/CircularBuffer.cpp
src/CommandBufferQueue.cpp
src/CommandStream.cpp
@@ -55,7 +54,6 @@ set(PRIVATE_HDRS
include/private/backend/HandleAllocator.h
include/private/backend/PlatformFactory.h
include/private/backend/SamplerGroup.h
src/CallbackManager.h
src/CommandStreamDispatcher.h
src/CompilerThreadPool.h
src/DataReshaper.h
@@ -69,6 +67,8 @@ set(PRIVATE_HDRS
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
list(APPEND SRCS
include/backend/platforms/OpenGLPlatform.h
src/opengl/CallbackManager.h
src/opengl/CallbackManager.cpp
src/opengl/gl_headers.cpp
src/opengl/gl_headers.h
src/opengl/GLUtils.cpp
@@ -355,7 +355,7 @@ if (MSVC)
set(FILAMENT_WARNINGS /W3)
else()
set(FILAMENT_WARNINGS
-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers
-Wall -Wextra -Wno-unused-parameter
-Wextra-semi -Wnewline-eof -Wdeprecated -Wundef
-Wgnu-conditional-omitted-operand
-Wweak-vtables -Wnon-virtual-dtor -Wclass-varargs -Wimplicit-fallthrough
@@ -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
+5 -17
View File
@@ -138,17 +138,6 @@ static constexpr const char* backendToString(Backend backend) {
}
}
/**
* Defines the shader language. Similar to the above backend enum, but the OpenGL backend can select
* between two shader languages: ESSL 1.0 and ESSL 3.0.
*/
enum class ShaderLanguage {
ESSL1 = 0,
ESSL3 = 1,
SPIRV = 2,
MSL = 3,
};
/**
* Bitmask for selecting render buffers
*/
@@ -824,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;
}
};
@@ -849,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
+2 -3
View File
@@ -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.
+11
View File
@@ -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 {
+67 -41
View File
@@ -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.
+11 -4
View File
@@ -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);
+7 -7
View File
@@ -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;
}
@@ -12,11 +12,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef TNT_FILAMENT_BACKEND_CALLBACKMANAGER_H
#define TNT_FILAMENT_BACKEND_CALLBACKMANAGER_H
#ifndef TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
#define TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
#include <backend/CallbackHandler.h>
@@ -96,4 +95,4 @@ private:
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_CALLBACKMANAGER_H
#endif // TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
@@ -208,8 +208,7 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
auto const& UTILS_RESTRICT usedBindingPoints = mUsedSamplerBindingPoints;
for (uint8_t i = 0, tmu = 0, n = mUsedBindingsCount; i < n; i++) {
size_t const binding = usedBindingPoints[i];
assert_invariant(binding < Program::SAMPLER_BINDING_COUNT);
auto const binding = usedBindingPoints[i];
auto const * const sb = samplerBindings[binding];
assert_invariant(sb);
for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here
@@ -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>;
+16 -18
View File
@@ -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);
@@ -377,19 +381,13 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
vkraster.colorTargetCount = 0;
mPipelineCache.bindRasterState(vkraster);
VulkanPipelineCache::VertexArray varray = {};
VkBuffer buffers[1] = {};
VkDeviceSize offsets[1] = {};
buffers[0] = mTriangleBuffer->getGpuBuffer();
VkVertexInputAttributeDescription attribDesc = {
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32_SFLOAT,
};
VkVertexInputBindingDescription bufferDesc = {
.binding = 0,
.stride = sizeof(float) * 2,
};
mPipelineCache.bindVertexArray(&attribDesc, &bufferDesc, 1);
varray.attributes[0] = { .location = 0, .binding = 0, .format = VK_FORMAT_R32G32_SFLOAT };
varray.buffers[0] = { .binding = 0, .stride = sizeof(float) * 2 };
mPipelineCache.bindVertexArray(varray);
// Select nearest filtering and clamp_to_edge.
VkSampler vksampler = mSamplerCache.getSampler({});
@@ -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 {
+108 -137
View File
@@ -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, &currentbuf->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 = &currentbuf->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 )
+11 -26
View File
@@ -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
+18 -70
View File
@@ -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) {
+105 -90
View File
@@ -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.
}
@@ -290,29 +300,20 @@ void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
}
void VulkanDriver::endFrame(uint32_t frameId) {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("endframe");
mCommands->flush();
collectGarbage();
FVK_SYSTRACE_END();
if (mCommands->flush()) {
collectGarbage();
}
}
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) {
@@ -372,7 +373,7 @@ void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {
auto bufferObject = mResourceAllocator.construct<VulkanBufferObject>(boh, mAllocator,
mStagePool, byteCount, bindingType);
mStagePool, byteCount, bindingType, usage);
mResourceManager.acquire(bufferObject);
}
@@ -525,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) {
@@ -690,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
@@ -871,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));
}
@@ -882,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));
}
@@ -893,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));
}
@@ -989,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);
@@ -1010,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();
}
@@ -1023,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;
@@ -1156,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()) {
@@ -1170,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.
@@ -1247,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;
@@ -1297,7 +1288,6 @@ void VulkanDriver::endRenderPass(int) {
}
mCurrentRenderPass.renderTarget = nullptr;
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
FVK_SYSTRACE_END();
}
void VulkanDriver::nextSubpass(int) {
@@ -1308,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);
@@ -1344,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
@@ -1362,19 +1349,17 @@ 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()) {
collectGarbage();
}
// 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) {
@@ -1406,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) {}
@@ -1440,6 +1423,7 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(),
std::move(pbd),
[&context = mContext](uint32_t reqs, VkFlags flags) {
utils::slog.d <<"read pixels: reqs=" << reqs <<" flags=" << flags << utils::io::endl;
return context.selectMemoryType(reqs, flags);
},
[this](PixelBufferDescriptor&& pbd) {
@@ -1455,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
@@ -1497,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;
@@ -1520,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;
}
@@ -1551,17 +1528,56 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
mPipelineCache.setCurrentRasterState(vkraster);
// Declare fixed-size arrays that get passed to the pipeCache and to vkCmdBindVertexBuffers.
uint32_t const bufferCount = prim.vertexBuffer->attributes.size();
VkVertexInputAttributeDescription const* attribDesc = prim.vertexBuffer->getAttribDescriptions();
VkVertexInputBindingDescription const* bufferDesc = prim.vertexBuffer->getBufferDescriptions();
VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers();
VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets();
VulkanPipelineCache::VertexArray varray = {};
VkBuffer buffers[MAX_VERTEX_ATTRIBUTE_COUNT] = {};
VkDeviceSize offsets[MAX_VERTEX_ATTRIBUTE_COUNT] = {};
// For each attribute, append to each of the above lists.
const uint32_t bufferCount = prim.vertexBuffer->attributes.size();
for (uint32_t attribIndex = 0; attribIndex < bufferCount; attribIndex++) {
Attribute attrib = prim.vertexBuffer->attributes[attribIndex];
const bool isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
const bool isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger);
// HACK: Re-use the positions buffer as a dummy buffer for disabled attributes. Filament's
// vertex shaders declare all attributes as either vec4 or uvec4 (the latter for bone
// indices), and positions are always at least 32 bits per element. Therefore we can assign
// a dummy type of either R8G8B8A8_UINT or R8G8B8A8_SNORM, depending on whether the shader
// expects to receive floats or ints.
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM;
attrib = prim.vertexBuffer->attributes[0];
}
const VulkanBuffer* buffer = prim.vertexBuffer->buffers[attrib.buffer];
// If the vertex buffer is missing a constituent buffer object, skip the draw call.
// There is no need to emit an error message because this is not explicitly forbidden.
if (buffer == nullptr) {
return;
}
buffers[attribIndex] = buffer->getGpuBuffer();
offsets[attribIndex] = attrib.offset;
varray.attributes[attribIndex] = {
.location = attribIndex, // matches the GLSL layout specifier
.binding = attribIndex, // matches the position within vkCmdBindVertexBuffers
.format = vkformat,
};
varray.buffers[attribIndex] = {
.binding = attribIndex,
.stride = attrib.stride,
};
}
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
mPipelineCache.bindProgram(*program);
mPipelineCache.bindRasterState(mPipelineCache.getCurrentRasterState());
mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology);
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount);
mPipelineCache.bindVertexArray(varray);
// Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding),
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
@@ -1597,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;
@@ -1671,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
+20 -71
View File
@@ -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
@@ -276,62 +271,16 @@ VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool&
uint32_t elementCount, AttributeArray const& attribs)
: HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
mInfo(new PipelineInfo(attribs.size())),
mResources(allocator) {
auto attribDesc = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
auto bufferDesc = mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
auto offsets = mInfo->mSoa.data<PipelineInfo::OFFSETS>();
auto attribToBufferIndex = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
std::fill(mInfo->mSoa.begin<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(),
mInfo->mSoa.end<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(), -1);
for (uint32_t attribIndex = 0; attribIndex < attribs.size(); attribIndex++) {
Attribute attrib = attribs[attribIndex];
bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger);
// HACK: Re-use the positions buffer as a dummy buffer for disabled attributes. Filament's
// vertex shaders declare all attributes as either vec4 or uvec4 (the latter for bone
// indices), and positions are always at least 32 bits per element. Therefore we can assign
// a dummy type of either R8G8B8A8_UINT or R8G8B8A8_SNORM, depending on whether the shader
// expects to receive floats or ints.
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM;
attrib = attribs[0];
}
offsets[attribIndex] = attrib.offset;
attribDesc[attribIndex] = {
.location = attribIndex,// matches the GLSL layout specifier
.binding = attribIndex, // matches the position within vkCmdBindVertexBuffers
.format = vkformat,
};
bufferDesc[attribIndex] = {
.binding = attribIndex,
.stride = attrib.stride,
};
attribToBufferIndex[attribIndex] = attrib.buffer;
}
}
VulkanVertexBuffer::~VulkanVertexBuffer() {
delete mInfo;
}
buffers(bufferCount, nullptr),
mResources(allocator) {}
void VulkanVertexBuffer::setBuffer(VulkanBufferObject* bufferObject, uint32_t index) {
size_t count = attributes.size();
auto vkbuffers = mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
auto attribToBuffer = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) {
if (attribToBuffer[attribIndex] == static_cast<int8_t>(index)) {
vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer();
}
}
buffers[index] = &bufferObject->buffer;
mResources.acquire(bufferObject);
}
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
uint32_t byteCount, BufferObjectBinding bindingType)
uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage)
: HwBufferObject(byteCount),
VulkanResource(VulkanResourceType::BUFFER_OBJECT),
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
+10 -41
View File
@@ -30,7 +30,6 @@
#include "private/backend/SamplerGroup.h"
#include <utils/Mutex.h>
#include <utils/StructureOfArrays.h>
namespace filament::backend {
@@ -95,50 +94,15 @@ struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
uint32_t elementCount, AttributeArray const& attributes);
~VulkanVertexBuffer();
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
inline VkVertexInputAttributeDescription const* getAttribDescriptions() {
return mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
inline void terminate() {
mResources.clear();
}
inline VkVertexInputBindingDescription const* getBufferDescriptions() {
return mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
}
inline VkBuffer const* getVkBuffers() const {
return mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
}
inline VkDeviceSize const* getOffsets() const {
return mInfo->mSoa.data<PipelineInfo::OFFSETS>();
}
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
private:
struct PipelineInfo {
PipelineInfo(size_t size)
: mSoa(size /* capacity */) {
mSoa.resize(size);
}
// These corresponds to the index of the element in the SoA
static constexpr uint8_t ATTRIBUTE_DESCRIPTION = 0;
static constexpr uint8_t BUFFER_DESCRIPTION = 1;
static constexpr uint8_t VK_BUFFER = 2;
static constexpr uint8_t OFFSETS = 3;
static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 4;
utils::StructureOfArrays<
VkVertexInputAttributeDescription,
VkVertexInputBindingDescription,
VkBuffer,
VkDeviceSize,
int8_t
> mSoa;
};
PipelineInfo* mInfo;
FixedSizeVulkanResourceManager mResources;
};
@@ -150,14 +114,19 @@ 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;
};
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
BufferObjectBinding bindingType);
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);
@@ -580,21 +580,14 @@ void VulkanPipelineCache::bindPrimitiveTopology(VkPrimitiveTopology topology) no
mPipelineRequirements.topology = topology;
}
void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
VkVertexInputBindingDescription const* bufferDesc, uint8_t count) {
void VulkanPipelineCache::bindVertexArray(const VertexArray& varray) noexcept {
for (size_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
if (i < count) {
mPipelineRequirements.vertexAttributes[i] = attribDesc[i];
mPipelineRequirements.vertexBuffers[i] = bufferDesc[i];
} else {
mPipelineRequirements.vertexAttributes[i] = {};
mPipelineRequirements.vertexBuffers[i] = {};
}
mPipelineRequirements.vertexAttributes[i] = varray.attributes[i];
mPipelineRequirements.vertexBuffers[i] = varray.buffers[i];
}
}
VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding(
uint32_t bindingIndex) const noexcept {
VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding(uint32_t bindingIndex) const noexcept {
auto& key = mDescriptorRequirements;
return {
key.uniformBuffers[bindingIndex],
@@ -685,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;
@@ -705,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]);
@@ -719,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 {
@@ -735,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;
@@ -762,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;
}
@@ -806,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
@@ -815,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
@@ -80,6 +80,8 @@ public:
// as a vertex assembler configuration. For simplicity it contains fixed-size arrays and does
// not store sizes; all unused entries are simply zeroed out.
struct VertexArray {
VkVertexInputAttributeDescription attributes[VERTEX_ATTRIBUTE_COUNT];
VkVertexInputBindingDescription buffers[VERTEX_ATTRIBUTE_COUNT];
};
// The ProgramBundle contains weak references to the compiled vertex and fragment shaders.
@@ -161,8 +163,7 @@ public:
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
void bindVertexArray(const VertexArray& varray) noexcept;
// Gets the current UBO at the given slot, useful for push / pop.
UniformBufferBinding getUniformBufferBinding(uint32_t bindingIndex) const noexcept;
@@ -339,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,
@@ -88,8 +88,8 @@ void VulkanSwapChain::present() {
.layerCount = 1,
};
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
mCommands->flush();
}
mCommands->flush();
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing);
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR
@@ -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;
+1 -1
View File
@@ -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;
+61
View File
@@ -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
+6
View File
@@ -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
-174
View File
@@ -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
+84 -1
View File
@@ -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) {
-245
View File
@@ -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>
+1 -1
View File
@@ -10,6 +10,6 @@ set(BENCHMARK_SRCS
add_executable(benchmark_filament ${BENCHMARK_SRCS})
target_link_libraries(benchmark_filament PRIVATE benchmark_main filament)
target_link_libraries(benchmark_filament PRIVATE benchmark_main utils math filament)
set_target_properties(benchmark_filament PROPERTIES FOLDER Benchmarks)
-2
View File
@@ -170,8 +170,6 @@ struct UTILS_PUBLIC Aabb {
value_type const* data() const { return vertices; }
value_type * data() { return vertices; }
size_t size() const { return 8; }
value_type const& operator[](size_t i) const noexcept { return vertices[i]; }
value_type& operator[](size_t i) noexcept { return vertices[i]; }
value_type vertices[8];
};
+2 -11
View File
@@ -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;
}
-3
View File
@@ -237,9 +237,6 @@ public:
//! Returns the vertex domain of this material.
VertexDomain getVertexDomain() const noexcept;
//! Returns the material's supported variants
UserVariantFilterMask getSupportedVariants() const noexcept;
//! Returns the material domain of this material.
//! The material domain determines how the material is used.
MaterialDomain getMaterialDomain() const noexcept;
+3 -11
View File
@@ -114,6 +114,8 @@ struct DynamicResolutionOptions {
* blendMode: Whether the bloom effect is purely additive (false) or mixed with the original
* image (true).
*
* anamorphism: Bloom's aspect ratio (x/y), for artistic purposes.
*
* threshold: When enabled, a threshold at 1.0 is applied on the source image, this is
* useful for artistic reasons and is usually needed when a dirt texture is used.
*
@@ -132,23 +134,13 @@ struct BloomOptions {
float dirtStrength = 0.2f; //!< strength of the dirt texture %codegen_skip_json% %codegen_skip_javascript%
float strength = 0.10f; //!< bloom's strength between 0.0 and 1.0
uint32_t resolution = 384; //!< resolution of vertical axis (2^levels to 2048)
float anamorphism = 1.0f; //!< bloom x/y aspect-ratio (1/32 to 32)
uint8_t levels = 6; //!< number of blur levels (1 to 11)
BlendMode blendMode = BlendMode::ADD; //!< how the bloom effect is applied
bool threshold = true; //!< whether to threshold the source
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
@@ -26,7 +26,6 @@
#include <utils/compiler.h>
#include <utils/EntityInstance.h>
#include <utils/FixedCapacityVector.h>
#include <math/mathfwd.h>
@@ -350,58 +349,6 @@ public:
Builder& skinning(size_t boneCount, Bone const* bones) noexcept; //!< \overload
Builder& skinning(size_t boneCount) noexcept; //!< \overload
/**
* Define bone indices and weights "pairs" for vertex skinning as a float2.
* The unsigned int(pair.x) defines index of the bone and pair.y is the bone weight.
* The pairs substitute \c BONE_INDICES and the \c BONE_WEIGHTS defined in the VertexBuffer.
* Both ways of indices and weights definition must not be combined in one primitive.
* Number of pairs per vertex bonesPerVertex is not limited to 4 bones.
* Vertex buffer used for \c primitiveIndex must be set for advance skinning.
* All bone weights of one vertex should sum to one. Otherwise they will be normalized.
* Data must be rectangular and number of bone pairs must be same for all vertices of this
* primitive.
* The data is arranged sequentially, all bone pairs for the first vertex, then for the
* second vertex, and so on.
*
* @param primitiveIndex zero-based index of the primitive, must be less than the primitive
* count passed to Builder constructor
* @param indicesAndWeights pairs of bone index and bone weight for all vertices
* sequentially
* @param count number of all pairs, must be a multiple of vertexCount of the primitive
* count = vertexCount * bonesPerVertex
* @param bonesPerVertex number of bone pairs, same for all vertices of the primitive
*
* @return Builder reference for chaining calls.
*
* @see VertexBuffer:Builder:advancedSkinning
*/
Builder& boneIndicesAndWeights(size_t primitiveIndex,
math::float2 const* indicesAndWeights, size_t count, size_t bonesPerVertex) noexcept;
/**
* Define bone indices and weights "pairs" for vertex skinning as a float2.
* The unsigned int(pair.x) defines index of the bone and pair.y is the bone weight.
* The pairs substitute \c BONE_INDICES and the \c BONE_WEIGHTS defined in the VertexBuffer.
* Both ways of indices and weights definition must not be combined in one primitive.
* Number of pairs is not limited to 4 bones per vertex.
* Vertex buffer used for \c primitiveIndex must be set for advance skinning.
* All bone weights of one vertex should sum to one. Otherwise they will be normalized.
* Data doesn't have to be rectangular and number of pairs per vertices of primitive can be
* variable.
* The vector of the vertices contains the vectors of the pairs
*
* @param primitiveIndex zero-based index of the primitive, must be less than the primitive
* count passed to Builder constructor
* @param indicesAndWeightsVectors pairs of bone index and bone weight for all vertices of
* the primitive sequentially
*
* @return Builder reference for chaining calls.
*
* @see VertexBuffer:Builder:advancedSkinning
*/
Builder& boneIndicesAndWeights(size_t primitiveIndex,
utils::FixedCapacityVector<
utils::FixedCapacityVector<math::float2>> indicesAndWeightsVector) noexcept;
/**
* Controls if the renderable has vertex morphing targets, zero by default. This is
* required to enable GPU morphing.
-13
View File
@@ -142,19 +142,6 @@ public:
*/
Builder& normalized(VertexAttribute attribute, bool normalize = true) noexcept;
/**
* Sets advanced skinning mode. Bone data, indices and weights will be
* set in RenderableManager:Builder:boneIndicesAndWeights methods.
* Works with or without buffer objects.
*
* @param enabled If true, enables advanced skinning mode. False by default.
*
* @return A reference to this Builder for chaining calls.
*
* @see RenderableManager:Builder:boneIndicesAndWeights
*/
Builder& advancedSkinning(bool enabled) noexcept;
/**
* Creates the VertexBuffer object and returns a pointer to it.
*
+2 -2
View File
@@ -31,14 +31,14 @@ struct FrameHistoryEntry {
struct TemporalAA{
FrameGraphTexture color;
FrameGraphTexture::Descriptor desc;
math::mat4 projection; // world space to clip space
math::mat4f projection; // world space to clip space
math::float2 jitter{};
uint32_t frameId = 0; // used for halton sequence
} taa;
struct {
FrameGraphTexture color;
FrameGraphTexture::Descriptor desc;
math::mat4 projection;
math::mat4f projection;
} ssr;
};
-4
View File
@@ -141,9 +141,5 @@ void Material::compile(CompilerPriorityQueue priority, UserVariantFilterMask var
downcast(this)->compile(priority, variantFilter, handler, std::move(callback));
}
UserVariantFilterMask Material::getSupportedVariants() const noexcept {
return downcast(this)->getSupportedVariants();
}
} // namespace filament
+12 -18
View File
@@ -44,27 +44,28 @@ namespace filament {
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParserDetails::MaterialParserDetails(ShaderLanguage language, const void* data, size_t size)
MaterialParser::MaterialParserDetails::MaterialParserDetails(Backend backend, const void* data, size_t size)
: mManagedBuffer(data, size),
mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()),
mMaterialChunk(mChunkContainer) {
switch (language) {
case ShaderLanguage::ESSL3:
switch (backend) {
case Backend::OPENGL:
mMaterialTag = ChunkType::MaterialGlsl;
mDictionaryTag = ChunkType::DictionaryText;
break;
case ShaderLanguage::ESSL1:
mMaterialTag = ChunkType::MaterialEssl1;
mDictionaryTag = ChunkType::DictionaryText;
break;
case ShaderLanguage::MSL:
case Backend::METAL:
mMaterialTag = ChunkType::MaterialMetal;
mDictionaryTag = ChunkType::DictionaryText;
break;
case ShaderLanguage::SPIRV:
case Backend::VULKAN:
mMaterialTag = ChunkType::MaterialSpirv;
mDictionaryTag = ChunkType::DictionarySpirv;
break;
default:
// this is for testing purpose -- for e.g.: with the NoopDriver
mMaterialTag = ChunkType::MaterialGlsl;
mDictionaryTag = ChunkType::DictionaryText;
break;
}
}
@@ -83,8 +84,8 @@ bool MaterialParser::MaterialParserDetails::getFromSimpleChunk(
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParser(ShaderLanguage language, const void* data, size_t size)
: mImpl(language, data, size) {
MaterialParser::MaterialParser(Backend backend, const void* data, size_t size)
: mImpl(backend, data, size) {
}
ChunkContainer& MaterialParser::getChunkContainer() noexcept {
@@ -258,13 +259,6 @@ bool MaterialParser::getVertexDomain(VertexDomain* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialVertexDomain, reinterpret_cast<uint8_t*>(value));
}
bool MaterialParser::getMaterialVariantFilterMask(UserVariantFilterMask* value) const noexcept {
static_assert(sizeof(UserVariantFilterMask) == sizeof(uint32_t),
"UserVariantFilterMask expected size is wrong");
return mImpl.getFromSimpleChunk(ChunkType::MaterialVariantFilterMask,
reinterpret_cast<UserVariantFilterMask*>(value));
}
bool MaterialParser::getMaterialDomain(MaterialDomain* value) const noexcept {
static_assert(sizeof(MaterialDomain) == sizeof(uint8_t),
"MaterialDomain expected size is wrong");
+2 -3
View File
@@ -48,7 +48,7 @@ struct MaterialConstant;
class MaterialParser {
public:
MaterialParser(backend::ShaderLanguage language, const void* data, size_t size);
MaterialParser(backend::Backend backend, const void* data, size_t size);
MaterialParser(MaterialParser const& rhs) noexcept = delete;
MaterialParser& operator=(MaterialParser const& rhs) noexcept = delete;
@@ -98,7 +98,6 @@ public:
bool getInterpolation(Interpolation* value) const noexcept;
bool getVertexDomain(VertexDomain* value) const noexcept;
bool getMaterialDomain(MaterialDomain* domain) const noexcept;
bool getMaterialVariantFilterMask(UserVariantFilterMask* userVariantFilterMask) const noexcept;
bool getShading(Shading*) const noexcept;
bool getBlendingMode(BlendingMode*) const noexcept;
@@ -129,7 +128,7 @@ public:
private:
struct MaterialParserDetails {
MaterialParserDetails(backend::ShaderLanguage language, const void* data, size_t size);
MaterialParserDetails(backend::Backend backend, const void* data, size_t size);
template<typename T>
bool getFromSimpleChunk(filamat::ChunkType type, T* value) const noexcept;
+5 -5
View File
@@ -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);
+161 -159
View File
@@ -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);
@@ -482,20 +481,23 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> history;
};
auto const& previous = frameHistory.getPrevious().ssr;
if (!previous.color.handle) {
return {};
mat4f historyProjection;
FrameGraphId<FrameGraphTexture> history;
FrameHistoryEntry const& entry = frameHistory[0];
if (entry.ssr.color.handle) {
// the first time around we may not have a history buffer
history = fg.import("SSR history", entry.ssr.desc,
FrameGraphTexture::Usage::SAMPLEABLE, entry.ssr.color);
historyProjection = entry.ssr.projection;
}
FrameGraphId<FrameGraphTexture> history = fg.import("SSR history", previous.desc,
FrameGraphTexture::Usage::SAMPLEABLE, previous.color);
mat4 const& historyProjection = previous.projection;
auto const& uvFromClipMatrix = mEngine.getUvFromClipMatrix();
auto& ssrPass = fg.addPass<SSRPassData>("SSR Pass",
[&](FrameGraph::Builder& builder, auto& data) {
// Create our reflection buffer. We need an alpha channel, so we have to use RGBA16F
// create our reflection buffer. We need an alpha channel, so we have to use RGBA16F
data.reflections = builder.createTexture("Reflections Texture", {
.width = desc.width, .height = desc.height,
.format = TextureFormat::RGBA16F });
@@ -534,8 +536,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
// set screen-space reflections and screen-space refractions
mat4f const uvFromViewMatrix = uvFromClipMatrix * projection;
mat4f const reprojection = uvFromClipMatrix *
mat4f{ historyProjection * inverse(userViewMatrix) };
mat4f const reprojection = mat4f{ uvFromClipMatrix * historyProjection
* inverse(userViewMatrix) };
// the history sampler is a regular texture2D
TextureHandle const history = data.history ?
@@ -1594,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);
@@ -1867,124 +1870,61 @@ 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;
// Anamorphic bloom by always scaling down one of the dimension -- we do this (as opposed
// to scaling up) so that the amount of blooming doesn't decrease. However, the resolution
// decreases, meaning that the user might need to adjust the BloomOptions::resolution and
// BloomOptions::levels.
if (inoutBloomOptions.anamorphism >= 1.0f) {
bloomWidth *= 1.0f / inoutBloomOptions.anamorphism;
} else {
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,
@@ -1993,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 };
@@ -2490,7 +2488,7 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv
auto& current = frameHistory.getCurrent().*pTaa;
// compute projection
current.projection = inoutCameraInfo->projection * inoutCameraInfo->getUserViewMatrix();
current.projection = mat4f{ inoutCameraInfo->projection * inoutCameraInfo->getUserViewMatrix() };
current.frameId = previous.frameId + 1;
// sample position within a pixel [-0.5, 0.5]
@@ -2524,16 +2522,18 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
auto const& previous = frameHistory.getPrevious().*pTaa;
auto& current = frameHistory.getCurrent().*pTaa;
// if we don't have a history yet, just use the current color buffer as history
FrameGraphId<FrameGraphTexture> colorHistory = input;
if (UTILS_LIKELY(previous.color.handle)) {
FrameGraphId<FrameGraphTexture> colorHistory;
mat4f historyProjection;
if (UTILS_UNLIKELY(!previous.color.handle)) {
// if we don't have a history yet, just use the current color buffer as history
colorHistory = input;
historyProjection = current.projection;
} else {
colorHistory = fg.import("TAA history", previous.desc,
FrameGraphTexture::Usage::SAMPLEABLE, previous.color);
historyProjection = previous.projection;
}
mat4 const& historyProjection = previous.color.handle ?
previous.projection : current.projection;
struct TAAData {
FrameGraphId<FrameGraphTexture> color;
FrameGraphId<FrameGraphTexture> depth;
@@ -2612,7 +2612,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
});
mi->setParameter("filterWeights", weights, 9);
mi->setParameter("reprojection",
mat4f{ historyProjection * inverse(current.projection) } *
historyProjection *
inverse(current.projection) *
normalizedToClip);
mi->commit(driver);
@@ -3035,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);
-5
View File
@@ -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;
+2 -13
View File
@@ -241,8 +241,7 @@ void RenderPass::instanceify(FEngine& engine) noexcept {
lhs.primitive.skinningHandle == rhs.primitive.skinningHandle &&
lhs.primitive.skinningOffset == rhs.primitive.skinningOffset &&
lhs.primitive.morphWeightBuffer == rhs.primitive.morphWeightBuffer &&
lhs.primitive.morphTargetBuffer == rhs.primitive.morphTargetBuffer &&
lhs.primitive.skinningTexture == rhs.primitive.skinningTexture ;
lhs.primitive.morphTargetBuffer == rhs.primitive.morphTargetBuffer;
});
uint32_t const instanceCount = e - curr;
@@ -586,8 +585,6 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
cmdColor.primitive.skinningHandle = skinning.handle;
cmdColor.primitive.skinningOffset = skinning.offset;
cmdColor.primitive.skinningTexture = skinning.handleSampler;
cmdColor.primitive.morphWeightBuffer = morphing.handle;
cmdColor.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle();
@@ -692,8 +689,6 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
cmdDepth.primitive.skinningHandle = skinning.handle;
cmdDepth.primitive.skinningOffset = skinning.offset;
cmdDepth.primitive.skinningTexture = skinning.handleSampler;
cmdDepth.primitive.morphWeightBuffer = morphing.handle;
cmdDepth.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle();
@@ -875,13 +870,10 @@ void RenderPass::Executor::execute(backend::DriverApi& driver,
info.skinningHandle,
info.skinningOffset * sizeof(PerRenderableBoneUib::BoneData),
sizeof(PerRenderableBoneUib));
// note: always bind the skinningTexture because the shader needs it.
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING,
info.skinningTexture);
// note: even if only skinning is enabled, binding morphTargetBuffer is needed.
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING,
info.morphTargetBuffer);
}
}
if (UTILS_UNLIKELY(info.morphWeightBuffer)) {
// Instead of using a UBO per primitive, we could also have a single UBO for all
@@ -890,9 +882,6 @@ void RenderPass::Executor::execute(backend::DriverApi& driver,
info.morphWeightBuffer);
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING,
info.morphTargetBuffer);
// note: even if only morphing is enabled, binding skinningTexture is needed.
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING,
info.skinningTexture);
}
driver.draw(pipeline, info.primitiveHandle, instanceCount);
+1 -2
View File
@@ -232,7 +232,6 @@ public:
backend::RasterState rasterState; // 4 bytes
backend::Handle<backend::HwRenderPrimitive> primitiveHandle; // 4 bytes
backend::Handle<backend::HwBufferObject> skinningHandle; // 4 bytes
backend::Handle<backend::HwSamplerGroup> skinningTexture; // 4 bytes
backend::Handle<backend::HwBufferObject> morphWeightBuffer; // 4 bytes
backend::Handle<backend::HwSamplerGroup> morphTargetBuffer; // 4 bytes
backend::Handle<backend::HwBufferObject> instanceBufferHandle; // 4 bytes
@@ -240,7 +239,7 @@ public:
uint32_t skinningOffset = 0; // 4 bytes
uint16_t instanceCount; // 2 bytes [MSb: user]
Variant materialVariant; // 1 byte
// uint8_t reserved[0] = {}; // 0 bytes
uint8_t reserved[4] = {}; // 4 bytes
static const uint16_t USER_INSTANCE_MASK = 0x8000u;
static const uint16_t INSTANCE_COUNT_MASK = 0x7fffu;
+351 -453
View File
File diff suppressed because it is too large Load Diff
+17 -50
View File
@@ -26,7 +26,6 @@
#include <backend/DriverApiForward.h>
#include <filament/Box.h>
#include <filament/Viewport.h>
#include <math/mat4.h>
@@ -105,8 +104,7 @@ public:
// scratch data: light's near/far expressed in light-space, calculated from the scene's
// content assuming the light is at the origin.
math::float2 lsCastersNearFar;
math::float2 lsReceiversNearFar;
math::float2 lsNearFar;
// Viewing camera's near/far expressed in view-space, calculated from the
// scene's content.
@@ -209,8 +207,6 @@ private:
uint8_t v0, v1, v2, v3;
};
using Corners = Aabb::Corners;
// 8 corners, 12 segments w/ 2 intersection max -- all of this twice (8 + 12 * 2) * 2 (768 bytes)
using FrustumBoxIntersection = std::array<math::float3, 64>;
@@ -219,50 +215,17 @@ private:
const ShadowMapInfo& shadowMapInfo,
const FLightManager::ShadowParams& params) noexcept;
struct DirectionalShadowBounds {
math::mat4f Mv;
float zNear;
float zFar;
FrustumBoxIntersection lsClippedShadowVolume;
size_t vertexCount;
bool visibleShadows = false;
};
static DirectionalShadowBounds computeDirectionalShadowBounds(
FEngine& engine,
math::float3 direction,
FLightManager::ShadowParams params,
filament::CameraInfo const& camera,
SceneInfo const& sceneInfo) noexcept;
static math::mat4f applyLISPSM(math::mat4f& Wp,
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
const math::mat4f& LMp,
const math::mat4f& Mv,
const math::mat4f& LMpMv,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
FrustumBoxIntersection const& wsShadowReceiverVolume, size_t vertexCount,
const math::float3& dir);
static inline math::mat4f computeLightRotation(math::float3 const& lsDirection) noexcept;
static inline math::mat4f computeFocusMatrix(
const math::mat4f& LMpMv,
const math::mat4f& WLMp,
Aabb const& wsShadowReceiversVolume,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
filament::CameraInfo const& camera, math::float2 const& csNearFar,
uint16_t shadowDimension, bool stable) noexcept;
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 Aabb computeLightFrustumBounds(const math::mat4f& lightView,
Aabb const& wsShadowReceiversVolume, Aabb const& wsShadowCastersVolume,
SceneInfo const& sceneInfo,
bool stable, bool focusShadowCasters, bool farUsesShadowCasters) noexcept;
static Corners computeFrustumCorners(const math::mat4f& projectionInverse,
math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
static inline void computeFrustumCorners(math::float3* out,
const math::mat4f& projectionViewInverse, math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
static inline math::float2 computeNearFar(math::mat4f const& view,
math::float3 const* wsVertices, size_t count) noexcept;
@@ -278,7 +241,10 @@ private:
math::float3 const* wsVertices, size_t count) noexcept;
static inline Aabb compute2DBounds(const math::mat4f& lightView,
Aabb const& volume) noexcept;
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,
math::float3 const* wsVertices, size_t count) noexcept;
@@ -297,8 +263,9 @@ private:
static size_t intersectFrustumWithBox(
FrustumBoxIntersection& outVertices,
math::mat4f const& projection, math::float2 const& csNearFar,
Aabb const& box);
Frustum const& wsFrustum,
math::float3 const* wsFrustumCorners,
Aabb const& wsBox);
static math::mat4f warpFrustum(float n, float f) noexcept;
@@ -314,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 },
+11 -24
View File
@@ -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);
+15 -3
View File
@@ -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;
+2 -2
View File
@@ -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;
}
+23 -175
View File
@@ -24,8 +24,11 @@
#include "details/VertexBuffer.h"
#include "details/IndexBuffer.h"
#include "details/InstanceBuffer.h"
#include "details/Texture.h"
#include "details/Material.h"
#include <private/filament/SibStructs.h>
#include "filament/RenderableManager.h"
@@ -34,7 +37,7 @@
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <unordered_map>
using namespace filament::math;
using namespace utils;
@@ -65,23 +68,13 @@ struct RenderableManager::BuilderDetails {
FSkinningBuffer* mSkinningBuffer = nullptr;
FInstanceBuffer* mInstanceBuffer = nullptr;
uint32_t mSkinningBufferOffset = 0;
utils::FixedCapacityVector<math::float2> mBoneIndicesAndWeights;
size_t mBoneIndicesAndWeightsCount = 0;
// bone indices and weights defined for primitive index
std::unordered_map<size_t, utils::FixedCapacityVector<
utils::FixedCapacityVector<math::float2>>> mBonePairs;
explicit BuilderDetails(size_t count)
: mEntries(count), mCulling(true), mCastShadows(false),
mReceiveShadows(true), mScreenSpaceContactShadows(false),
mSkinningBufferMode(false), mFogEnabled(true), mBonePairs() {
: mEntries(count), mCulling(true), mCastShadows(false), mReceiveShadows(true),
mScreenSpaceContactShadows(false), mSkinningBufferMode(false), mFogEnabled(true) {
}
// this is only needed for the explicit instantiation below
BuilderDetails() = default;
void processBoneIndicesAndWights(Engine& engine, utils::Entity entity);
};
using BuilderType = RenderableManager;
@@ -212,26 +205,6 @@ RenderableManager::Builder& RenderableManager::Builder::enableSkinningBuffers(bo
return *this;
}
RenderableManager::Builder& RenderableManager::Builder::boneIndicesAndWeights(size_t primitiveIndex,
math::float2 const* indicesAndWeights, size_t count, size_t bonesPerVertex) noexcept {
size_t vertexCount = count / bonesPerVertex;
utils::FixedCapacityVector<utils::FixedCapacityVector<filament::math::float2>> bonePairs(vertexCount);
for ( size_t iVertex = 0; iVertex < vertexCount; iVertex++) {
utils::FixedCapacityVector<float2> vertexData(bonesPerVertex);
std::copy_n(indicesAndWeights + iVertex * bonesPerVertex,
bonesPerVertex, vertexData.data());
bonePairs[iVertex] = std::move(vertexData);
}
return boneIndicesAndWeights(primitiveIndex, bonePairs);
}
RenderableManager::Builder& RenderableManager::Builder::boneIndicesAndWeights(size_t primitiveIndex,
utils::FixedCapacityVector<
utils::FixedCapacityVector<math::float2>> indicesAndWeightsVector) noexcept {
mImpl->mBonePairs[primitiveIndex] = std::move(indicesAndWeightsVector);
return *this;
}
RenderableManager::Builder& RenderableManager::Builder::fog(bool enabled) noexcept {
mImpl->mFogEnabled = enabled;
return *this;
@@ -270,134 +243,25 @@ RenderableManager::Builder& RenderableManager::Builder::globalBlendOrderEnabled(
return *this;
}
UTILS_NOINLINE
void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engine, Entity entity) {
size_t maxPairsCount = 0; //size of texture, number of bone pairs
size_t maxPairsCountPerVertex = 0; //maximum of number of bone per vertex
for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair){
auto primitiveIndex = iBonePair->first;
auto entries = mEntries;
ASSERT_PRECONDITION(primitiveIndex < entries.size() && primitiveIndex >= 0,
"[primitive @ %u] primitiveindex is out of size (%u)", primitiveIndex, entries.size());
auto entry = mEntries[primitiveIndex];
auto bonePairsForPrimitive = iBonePair->second;
auto vertexCount = entry.vertices->getVertexCount();
ASSERT_PRECONDITION(bonePairsForPrimitive.size() == vertexCount,
"[primitive @ %u] bone indices and weights pairs count (%u) must be equal to vertex count (%u)",
primitiveIndex, bonePairsForPrimitive.size(), vertexCount);
auto const& declaredAttributes = downcast(entry.vertices)->getDeclaredAttributes();
ASSERT_PRECONDITION(declaredAttributes[VertexAttribute::BONE_INDICES]
|| declaredAttributes[VertexAttribute::BONE_WEIGHTS],
"[entity=%u, primitive @ %u] for advanced skinning set VertexBuffer::Builder::advancedSkinning()",
entity.getId(), primitiveIndex);
for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) {
size_t bonesPerVertex = bonePairsForPrimitive[iVertex].size();
maxPairsCount += bonesPerVertex;
maxPairsCountPerVertex = std::max(bonesPerVertex, maxPairsCountPerVertex);
}
}
size_t pairsCount = 0; // counting of number of pairs stored in texture
if (maxPairsCount) { // at least one primitive has bone indices and weights
// final texture data, indices and weights
mBoneIndicesAndWeights = utils::FixedCapacityVector<float2>(maxPairsCount);
// temporary indices and weights for one vertex
std::unique_ptr<float2[]> tempPairs = std::make_unique<float2[]>
(maxPairsCountPerVertex);
for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair) {
auto primitiveIndex = iBonePair->first;
auto bonePairsForPrimitive = iBonePair->second;
if (!bonePairsForPrimitive.size()) {
continue;
}
size_t vertexCount = mEntries[primitiveIndex].vertices->getVertexCount();
std::unique_ptr<uint16_t[]> skinJoints = std::make_unique<uint16_t[]>
(4 * vertexCount); // temporary indices for one vertex
std::unique_ptr<float[]> skinWeights = std::make_unique<float[]>
(4 * vertexCount); // temporary weights for one vertex
for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) {
size_t tempPairCount = 0;
float boneWeightsSum = 0;
for (size_t k = 0; k < bonePairsForPrimitive[iVertex].size(); k++) {
auto boneWeight = bonePairsForPrimitive[iVertex][k][1];
auto boneIndex = bonePairsForPrimitive[iVertex][k][0];
ASSERT_PRECONDITION(boneWeight >= 0,
"[entity=%u, primitive @ %u] bone weight (%f) of vertex=%u is negative ",
entity.getId(), primitiveIndex, boneWeight, iVertex);
if (boneWeight) {
ASSERT_PRECONDITION(boneIndex >= 0,
"[entity=%u, primitive @ %u] bone index (%i) of vertex=%u is negative ",
entity.getId(), primitiveIndex, (int) boneIndex, iVertex);
ASSERT_PRECONDITION(boneIndex < mSkinningBoneCount,
"[entity=%u, primitive @ %u] bone index (%i) of vertex=%u is bigger then bone count (%u) ",
entity.getId(), primitiveIndex, (int) boneIndex, iVertex, mSkinningBoneCount);
boneWeightsSum += boneWeight;
tempPairs[tempPairCount][0] = boneIndex;
tempPairs[tempPairCount][1] = boneWeight;
tempPairCount++;
}
}
ASSERT_PRECONDITION(boneWeightsSum > 0,
"[entity=%u, primitive @ %u] sum of bone weights of vertex=%u is %f, it should be positive.",
entity.getId(), primitiveIndex, iVertex, boneWeightsSum);
if (abs(boneWeightsSum - 1.f) > std::numeric_limits<float>::epsilon()) {
utils::slog.w << "Warning of skinning: [entity=%" << entity.getId()
<< ", primitive @ %" << primitiveIndex
<< "] sum of bone weights of vertex=" << iVertex << " is " << boneWeightsSum
<< ", it should be one. Weights will be normalized." << utils::io::endl;
}
// prepare data for vertex attributes
auto offset = iVertex * 4;
// set attributes, indices and weights, for <= 4 pairs
for (size_t j = 0, c = std::min((int) tempPairCount, 4); j < c; j++) {
skinJoints[j + offset] = tempPairs[j][0];
skinWeights[j + offset] = tempPairs[j][1] / boneWeightsSum;
}
// prepare data for texture
if (tempPairCount > 4) { // set attributes, indices and weights, for > 4 pairs
skinWeights[3 + offset] = -(float) (pairsCount + 1); // negative offset to texture 0..-1, 1..-2
skinJoints[3 + offset] = (uint16_t) tempPairCount; // number pairs per vertex in texture
for (size_t j = 3; j < tempPairCount; j++) {
mBoneIndicesAndWeights[pairsCount][0] = tempPairs[j][0];
mBoneIndicesAndWeights[pairsCount][1] = tempPairs[j][1] / boneWeightsSum;
pairsCount++;
}
}
} // for all vertices per primitive
downcast(mEntries[primitiveIndex].vertices)
->updateBoneIndicesAndWeights(downcast(engine),
std::move(skinJoints),
std::move(skinWeights));
} // for all primitives
}
mBoneIndicesAndWeightsCount = pairsCount; // only part of mBoneIndicesAndWeights is used for real data
}
RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& engine, Entity entity) {
bool isEmpty = true;
ASSERT_PRECONDITION(mImpl->mSkinningBoneCount <= CONFIG_MAX_BONE_COUNT,
"bone count > %u", CONFIG_MAX_BONE_COUNT);
ASSERT_PRECONDITION(mImpl->mInstanceCount <= CONFIG_MAX_INSTANCES || !mImpl->mInstanceBuffer,
"instance count is %zu, but instance count is limited to CONFIG_MAX_INSTANCES (%zu) "
"instances when supplying transforms via an InstanceBuffer.",
mImpl->mInstanceCount,
CONFIG_MAX_INSTANCES);
ASSERT_PRECONDITION(
mImpl->mInstanceCount <= engine.getMaxAutomaticInstances() || !mImpl->mInstanceBuffer,
"instance count is %zu, but instance count is limited to "
"Engine::getMaxAutomaticInstances() (%zu) instances when supplying transforms via an "
"InstanceBuffer.",
mImpl->mInstanceCount, engine.getMaxAutomaticInstances());
if (mImpl->mInstanceBuffer) {
size_t bufferInstanceCount = mImpl->mInstanceBuffer->mInstanceCount;
ASSERT_PRECONDITION(mImpl->mInstanceCount <= bufferInstanceCount,
"instance count (%zu) must be less than or equal to the InstanceBuffer's instance "
"count "
"(%zu).",
"count (%zu).",
mImpl->mInstanceCount, bufferInstanceCount);
}
if (UTILS_LIKELY(mImpl->mSkinningBoneCount || mImpl->mSkinningBufferMode)) {
mImpl->processBoneIndicesAndWights(engine, entity);
}
for (size_t i = 0, c = mImpl->mEntries.size(); i < c; i++) {
auto& entry = mImpl->mEntries[i];
@@ -423,12 +287,12 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
// reject invalid geometry parameters
ASSERT_PRECONDITION(entry.offset + entry.count <= entry.indices->getIndexCount(),
"[entity=%u, primitive @ %u] offset (%u) + count (%u) > indexCount (%u)",
entity.getId(), i,
i, entity.getId(),
entry.offset, entry.count, entry.indices->getIndexCount());
ASSERT_PRECONDITION(entry.minIndex <= entry.maxIndex,
"[entity=%u, primitive @ %u] minIndex (%u) > maxIndex (%u)",
entity.getId(), i,
i, entity.getId(),
entry.minIndex, entry.maxIndex);
// this can't be an error because (1) those values are not immutable, so the caller
@@ -598,28 +462,16 @@ void FRenderableManager::create(
// Create and initialize all needed MorphTargets.
// It's required to avoid branches in hot loops.
MorphTargets* const morphTargets = new MorphTargets[entryCount];
std::generate_n(morphTargets, entryCount,
[dummy = mEngine.getDummyMorphTargetBuffer()]() -> MorphTargets {
return { dummy, 0, 0 };
});
MorphTargets* morphTargets = new MorphTargets[entryCount];
for (size_t i = 0; i < entryCount; ++i) {
morphTargets[i] = { mEngine.getDummyMorphTargetBuffer(), 0, 0 };
}
mManager[ci].morphTargets = { morphTargets, size_type(entryCount) };
// Always create skinning and morphing resources if one of them is enabled because
// the shader always handles both. See Variant::SKINNING_OR_MORPHING.
// Even morphing isn't enabled, we should create morphig resources.
// Because morphing shader code is generated when skinning is enabled.
// You can see more detail at Variant::SKINNING_OR_MORPHING.
if (UTILS_UNLIKELY(boneCount > 0 || targetCount > 0)) {
auto [sampler, texture] = FSkinningBuffer::createIndicesAndWeightsHandle(
downcast(engine), builder->mBoneIndicesAndWeightsCount);
if (builder->mBoneIndicesAndWeightsCount > 0) {
FSkinningBuffer::setIndicesAndWeightsData(downcast(engine), texture,
builder->mBoneIndicesAndWeights, builder->mBoneIndicesAndWeightsCount);
}
Bones& bones = manager[ci].bones;
bones.handleSamplerGroup = sampler;
bones.handleTexture = texture;
// Instead of using a UBO per primitive, we could also have a single UBO for all primitives
// and use bindUniformBufferRange which might be more efficient.
MorphWeights& morphWeights = manager[ci].morphWeights;
@@ -643,7 +495,7 @@ void FRenderableManager::create(
// morphWeights uniform array to avoid crash on adreno gpu.
if (UTILS_UNLIKELY(targetCount == 0 &&
driver.isWorkaroundNeeded(Workaround::ADRENO_UNIFORM_ARRAY_CRASH))) {
float initWeights[1] = { 0 };
float initWeights[1] = {0};
setMorphWeights(ci, initWeights, 1, 0);
}
}
@@ -697,10 +549,6 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept {
if (bones.handle && !bones.skinningBufferMode) {
driver.destroyBufferObject(bones.handle);
}
if (bones.handleSamplerGroup){
driver.destroySamplerGroup(bones.handleSamplerGroup);
driver.destroyTexture(bones.handleTexture);
}
// destroy the weights structures if any
MorphWeights const& morphWeights = manager[ci].morphWeights;
+2 -5
View File
@@ -151,7 +151,6 @@ public:
struct SkinningBindingInfo {
backend::Handle<backend::HwBufferObject> handle;
uint32_t offset;
backend::Handle<backend::HwSamplerGroup> handleSampler;
};
inline SkinningBindingInfo getSkinningBufferInfo(Instance instance) const noexcept;
@@ -209,10 +208,8 @@ private:
uint16_t count = 0;
uint16_t offset = 0;
bool skinningBufferMode = false;
backend::Handle<backend::HwSamplerGroup> handleSamplerGroup;
backend::Handle<backend::HwTexture> handleTexture;
};
static_assert(sizeof(Bones) == 20);
static_assert(sizeof(Bones) == 12);
struct MorphWeights {
backend::Handle<backend::HwBufferObject> handle;
@@ -413,7 +410,7 @@ Box const& FRenderableManager::getAABB(Instance instance) const noexcept {
FRenderableManager::SkinningBindingInfo
FRenderableManager::getSkinningBufferInfo(Instance instance) const noexcept {
Bones const& bones = mManager[instance].bones;
return { bones.handle, bones.offset, bones.handleSamplerGroup };
return { bones.handle, bones.offset };
}
inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexcept {
-16
View File
@@ -224,22 +224,6 @@ public:
return mPlatform;
}
backend::ShaderLanguage getShaderLanguage() const noexcept {
switch (mBackend) {
case Backend::DEFAULT:
case Backend::NOOP:
default:
return backend::ShaderLanguage::ESSL3;
case Backend::OPENGL:
return mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0
? backend::ShaderLanguage::ESSL1 : backend::ShaderLanguage::ESSL3;
case Backend::VULKAN:
return backend::ShaderLanguage::SPIRV;
case Backend::METAL:
return backend::ShaderLanguage::MSL;
}
}
ResourceAllocator& getResourceAllocator() noexcept {
assert_invariant(mResourceAllocator);
return *mResourceAllocator;

Some files were not shown because too many files have changed in this diff Show More