Merge branch 'rc/1.43.0' into release
This commit is contained in:
132
BUILDING.md
132
BUILDING.md
@@ -40,20 +40,20 @@ inside the Filament source tree.
|
||||
|
||||
To trigger an incremental debug build:
|
||||
|
||||
```
|
||||
$ ./build.sh debug
|
||||
```shell
|
||||
./build.sh debug
|
||||
```
|
||||
|
||||
To trigger an incremental release build:
|
||||
|
||||
```
|
||||
$ ./build.sh release
|
||||
```shell
|
||||
./build.sh release
|
||||
```
|
||||
|
||||
To trigger both incremental debug and release builds:
|
||||
|
||||
```
|
||||
$ ./build.sh debug release
|
||||
```shell
|
||||
./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:
|
||||
|
||||
```
|
||||
$ cd <cmake-build-directory>
|
||||
$ cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF
|
||||
```shell
|
||||
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.
|
||||
|
||||
```
|
||||
$ mkdir out/cmake-release
|
||||
$ cd out/cmake-release
|
||||
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
```shell
|
||||
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:
|
||||
|
||||
```
|
||||
$ mkdir out/cmake-release
|
||||
$ cd out/cmake-release
|
||||
```shell
|
||||
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:
|
||||
|
||||
```
|
||||
$ 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
|
||||
```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
|
||||
```
|
||||
|
||||
Finally, invoke `ninja`:
|
||||
|
||||
```
|
||||
$ ninja
|
||||
```shell
|
||||
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:
|
||||
|
||||
```
|
||||
$ xcode-select --install
|
||||
```shell
|
||||
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:
|
||||
|
||||
```
|
||||
$ mkdir out/cmake-release
|
||||
$ cd out/cmake-release
|
||||
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
$ ninja
|
||||
```shell
|
||||
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:
|
||||
|
||||
```
|
||||
$ ./build.sh -p ios debug
|
||||
```shell
|
||||
./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:
|
||||
|
||||
```
|
||||
> mkdir out
|
||||
> cd out
|
||||
> cmake ..
|
||||
```bat
|
||||
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:
|
||||
|
||||
```
|
||||
> samples\Debug\material_sandbox.exe ..\assets\models\monkey\monkey.obj
|
||||
```bat
|
||||
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.
|
||||
|
||||
```
|
||||
> cmake --build . --target gltf_viewer --config Release
|
||||
```bat
|
||||
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:
|
||||
|
||||
```
|
||||
$ ./build.sh -p android release
|
||||
```shell
|
||||
./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`).
|
||||
|
||||
```
|
||||
$ 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 ../..
|
||||
```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 ../..
|
||||
```
|
||||
|
||||
And then invoke `ninja`:
|
||||
|
||||
```
|
||||
$ ninja install
|
||||
```shell
|
||||
ninja install
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
$ ninja install/strip
|
||||
```shell
|
||||
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:
|
||||
|
||||
```
|
||||
$ ./gradlew -Pcom.google.android.filament.dist-dir=../../out/android-release/filament assembleRelease
|
||||
```shell
|
||||
./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:
|
||||
|
||||
```
|
||||
$ cd filament/filament
|
||||
$ doxygen docs/doxygen/filament.doxygen
|
||||
```shell
|
||||
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
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@ again.
|
||||
|
||||
## Code Style
|
||||
|
||||
See [CodeStyle.md](/CODE_STYLE.md)
|
||||
See [CODE_STYLE.md](/CODE_STYLE.md)
|
||||
|
||||
## Code reviews
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.42.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.43.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:
|
||||
|
||||
```
|
||||
pod 'Filament', '~> 1.42.2'
|
||||
```shell
|
||||
pod 'Filament', '~> 1.43.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,6 +7,11 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.43.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
|
||||
|
||||
@@ -258,6 +258,7 @@ 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;
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ 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
|
||||
@@ -69,10 +70,12 @@ set(GLTFIO_SRCS
|
||||
${GLTFIO_DIR}/src/FilamentAsset.cpp
|
||||
${GLTFIO_DIR}/src/FilamentInstance.cpp
|
||||
${GLTFIO_DIR}/src/FNodeManager.h
|
||||
${GLTFIO_DIR}/src/FTrsTransformManager.h
|
||||
${GLTFIO_DIR}/src/GltfEnums.h
|
||||
${GLTFIO_DIR}/src/Ktx2Provider.cpp
|
||||
${GLTFIO_DIR}/src/MaterialProvider.cpp
|
||||
${GLTFIO_DIR}/src/NodeManager.cpp
|
||||
${GLTFIO_DIR}/src/TrsTransformManager.cpp
|
||||
${GLTFIO_DIR}/src/ResourceLoader.cpp
|
||||
${GLTFIO_DIR}/src/StbProvider.cpp
|
||||
${GLTFIO_DIR}/src/TangentsJob.cpp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.42.2
|
||||
VERSION_NAME=1.43.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -87,9 +87,9 @@ compile Filament's native library and Filament's AAR for this project. The easie
|
||||
is to install all the required dependencies and to run the following commands at the root of the
|
||||
source tree:
|
||||
|
||||
```
|
||||
$ ./build.sh -p desktop -i release
|
||||
$ ./build.sh -p android release
|
||||
```shell
|
||||
./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:
|
||||
```
|
||||
$ ./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
|
||||
```shell
|
||||
./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
|
||||
```
|
||||
|
||||
## Important: SDK location
|
||||
@@ -110,14 +110,24 @@ 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.
|
||||
|
||||
## Android Studio
|
||||
## Compiling
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
```
|
||||
$ cd filament/filament
|
||||
$ doxygen docs/doxygen/filament.doxygen
|
||||
```shell
|
||||
cd filament/filament
|
||||
doxygen docs/doxygen/filament.doxygen
|
||||
```
|
||||
|
||||
Finally simply open `docs/html/index.html` in your web browser.
|
||||
|
||||
@@ -412,6 +412,7 @@ if (APPLE)
|
||||
test/test_RenderExternalImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(backend_test PRIVATE
|
||||
|
||||
@@ -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->updateLodRange(buffer.level);
|
||||
colorTexture->extendLodRangeTo(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->updateLodRange(depth.level);
|
||||
depthTexture->extendLodRangeTo(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->updateLodRange(stencil.level);
|
||||
stencilTexture->extendLodRangeTo(stencil.level);
|
||||
stencilAttachment = { stencilTexture, stencil.level, stencil.layer };
|
||||
}
|
||||
|
||||
@@ -789,6 +789,8 @@ 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,
|
||||
@@ -900,14 +902,13 @@ 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 bind what we can now and wait
|
||||
// until draw call time to bind any special cases (done in finalizeSamplerGroup).
|
||||
// the argument buffer representing the sampler group. So, we wait until draw call time to bind
|
||||
// textures (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.
|
||||
// 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.
|
||||
// finalizeSamplerGroup 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 texture / sampler to empty slots.
|
||||
@@ -930,27 +931,6 @@ 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));
|
||||
@@ -1376,8 +1356,8 @@ void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
}
|
||||
|
||||
void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
// 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.
|
||||
// All the id<MTLSamplerState> objects have already been bound to the argument buffer.
|
||||
// Here we bind all the textures.
|
||||
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(mContext);
|
||||
|
||||
|
||||
@@ -217,21 +217,14 @@ public:
|
||||
void generateMipmaps() noexcept;
|
||||
|
||||
// A texture starts out with none of its mip levels (also referred to as LODs) available for
|
||||
// reading. 3 actions update the range of LODs available:
|
||||
// reading. 4 actions update the range of LODs available:
|
||||
// - calling loadImage
|
||||
// - calling generateMipmaps
|
||||
// - using the texture as a render target attachment
|
||||
// The range of available mips can only increase, never decrease.
|
||||
// - calling setMinMaxLevels
|
||||
// A texture's available mips are consistent throughout a render pass.
|
||||
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;
|
||||
}
|
||||
void setLodRange(uint32_t minLevel, uint32_t maxLevel);
|
||||
void extendLodRangeTo(uint32_t level);
|
||||
|
||||
static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format);
|
||||
|
||||
|
||||
@@ -513,7 +513,7 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context),
|
||||
externalImage(context) {
|
||||
texture = metalTexture;
|
||||
updateLodRange(0, levels - 1);
|
||||
setLodRange(0, levels - 1);
|
||||
}
|
||||
|
||||
MetalTexture::~MetalTexture() {
|
||||
@@ -658,14 +658,14 @@ void MetalTexture::loadImage(uint32_t level, MTLRegion region, PixelBufferDescri
|
||||
}
|
||||
}
|
||||
|
||||
updateLodRange(level);
|
||||
extendLodRangeTo(level);
|
||||
}
|
||||
|
||||
void MetalTexture::generateMipmaps() noexcept {
|
||||
id <MTLBlitCommandEncoder> blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder];
|
||||
[blitEncoder generateMipmapsForTexture:texture];
|
||||
[blitEncoder endEncoding];
|
||||
updateLodRange(0, texture.mipmapLevelCount - 1);
|
||||
setLodRange(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::updateLodRange(uint32_t level) {
|
||||
void MetalTexture::extendLodRangeTo(uint32_t level) {
|
||||
assert_invariant(!isInRenderPass(&context));
|
||||
minLod = std::min(minLod, level);
|
||||
maxLod = std::max(maxLod, level);
|
||||
lodTextureView = nil;
|
||||
}
|
||||
|
||||
void MetalTexture::updateLodRange(uint32_t min, uint32_t max) {
|
||||
void MetalTexture::setLodRange(uint32_t min, uint32_t max) {
|
||||
assert_invariant(!isInRenderPass(&context));
|
||||
assert_invariant(min <= max);
|
||||
minLod = std::min(minLod, min);
|
||||
maxLod = std::max(maxLod, max);
|
||||
minLod = min;
|
||||
maxLod = max;
|
||||
lodTextureView = nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
token->compiler.cancelTickOp(token);
|
||||
bool canceled = token->compiler.cancelTickOp(token);
|
||||
|
||||
if (token->compiler.mShaderCompilerThreadCount) {
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
@@ -354,7 +354,7 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
// order for future callbacks to be successfully called.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
} else {
|
||||
} else if (canceled) {
|
||||
// Since the tick op was canceled, we need to .put the token here.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
@@ -683,7 +683,7 @@ void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
|
||||
}
|
||||
|
||||
void ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
||||
bool 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,9 +691,11 @@ void 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;
|
||||
void cancelTickOp(program_token_t token) noexcept;
|
||||
bool cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
|
||||
@@ -147,7 +147,7 @@ void VulkanBlitter::blitColor(BlitArgs args) {
|
||||
}
|
||||
#endif
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
|
||||
@@ -184,7 +184,7 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
@@ -197,13 +197,11 @@ void VulkanBlitter::terminate() noexcept {
|
||||
mDepthResolveProgram = nullptr;
|
||||
|
||||
if (mTriangleBuffer) {
|
||||
mTriangleBuffer->terminate();
|
||||
delete mTriangleBuffer;
|
||||
mTriangleBuffer = nullptr;
|
||||
}
|
||||
|
||||
if (mParamsBuffer) {
|
||||
mParamsBuffer->terminate();
|
||||
delete mParamsBuffer;
|
||||
mParamsBuffer = nullptr;
|
||||
}
|
||||
@@ -257,7 +255,7 @@ void VulkanBlitter::lazyInit() noexcept {
|
||||
};
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
|
||||
mTriangleBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
sizeof(kTriangleVertices));
|
||||
@@ -278,7 +276,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
||||
lazyInit();
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
commands->acquire(src.texture);
|
||||
commands->acquire(dst.texture);
|
||||
|
||||
|
||||
@@ -45,14 +45,7 @@ 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,7 +28,6 @@ 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 {
|
||||
|
||||
@@ -35,31 +35,38 @@ namespace filament::backend {
|
||||
|
||||
using Timestamp = VulkanGroupMarkers::Timestamp;
|
||||
|
||||
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);
|
||||
|
||||
VulkanCmdFence::VulkanCmdFence(VkFence ifence)
|
||||
: fence(ifence) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
VulkanCmdFence::~VulkanCmdFence() {
|
||||
vkDestroyFence(device, fence, VKALLOC);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -106,7 +113,7 @@ std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
|
||||
assert_invariant(!empty());
|
||||
auto const marker = mMarkers.back();
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const topTimestamp = mTimestamps.top();
|
||||
auto const topTimestamp = mTimestamps.front();
|
||||
return std::make_pair(marker, topTimestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
@@ -129,8 +136,13 @@ 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);
|
||||
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator, mDevice, mPool);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,9 +150,12 @@ 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() {
|
||||
@@ -151,11 +166,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 (mAvailableCount == 0) {
|
||||
while (mAvailableBufferCount == 0) {
|
||||
#if VK_REPORT_STALLS
|
||||
slog.i << "VulkanCommands has stalled. "
|
||||
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
|
||||
<< io::endl;
|
||||
slog.i << "VulkanCommands has stalled. "
|
||||
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
|
||||
<< io::endl;
|
||||
#endif
|
||||
wait();
|
||||
gc();
|
||||
@@ -165,7 +180,7 @@ VulkanCommandBuffer& VulkanCommands::get() {
|
||||
// Find an available slot.
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||
if (wrapper->buffer() == VK_NULL_HANDLE) {
|
||||
mCurrentCommandBufferIndex = static_cast<int8_t>(i);
|
||||
currentbuf = wrapper;
|
||||
break;
|
||||
@@ -173,28 +188,19 @@ VulkanCommandBuffer& VulkanCommands::get() {
|
||||
}
|
||||
|
||||
assert_invariant(currentbuf);
|
||||
--mAvailableCount;
|
||||
|
||||
// Create the low-level command buffer.
|
||||
const VkCommandBufferAllocateInfo allocateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = mPool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1
|
||||
};
|
||||
vkAllocateCommandBuffers(mDevice, &allocateInfo, ¤tbuf->cmdbuffer);
|
||||
mAvailableBufferCount--;
|
||||
|
||||
// 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>(mDevice);
|
||||
currentbuf->fence = std::make_shared<VulkanCmdFence>(mFences[mCurrentCommandBufferIndex]);
|
||||
|
||||
// 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->cmdbuffer, &binfo);
|
||||
vkBeginCommandBuffer(currentbuf->buffer(), &binfo);
|
||||
|
||||
// Notify the observer that a new command buffer has been activated.
|
||||
if (mObserver) {
|
||||
@@ -207,7 +213,6 @@ VulkanCommandBuffer& VulkanCommands::get() {
|
||||
auto [marker, time] = mCarriedOverMarkers->pop();
|
||||
pushGroupMarker(marker.c_str(), time);
|
||||
}
|
||||
|
||||
return *currentbuf;
|
||||
}
|
||||
|
||||
@@ -217,7 +222,6 @@ bool VulkanCommands::flush() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Before actually submitting, we need to pop any leftover group markers.
|
||||
// Note that this needs to occur before vkEndCommandBuffer.
|
||||
while (mGroupMarkers && !mGroupMarkers->empty()) {
|
||||
@@ -235,7 +239,7 @@ bool VulkanCommands::flush() {
|
||||
VulkanCommandBuffer const* currentbuf = mStorage[index].get();
|
||||
VkSemaphore const renderingFinished = mSubmissionSignals[index];
|
||||
|
||||
vkEndCommandBuffer(currentbuf->cmdbuffer);
|
||||
vkEndCommandBuffer(currentbuf->buffer());
|
||||
|
||||
// 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
|
||||
@@ -244,24 +248,26 @@ 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,
|
||||
};
|
||||
|
||||
VkSubmitInfo submitInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = 0,
|
||||
.pWaitSemaphores = signals,
|
||||
.pWaitDstStageMask = waitDestStageMasks,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = ¤tbuf->cmdbuffer,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &renderingFinished,
|
||||
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,
|
||||
};
|
||||
|
||||
if (mSubmissionSignal) {
|
||||
@@ -278,10 +284,11 @@ bool VulkanCommands::flush() {
|
||||
}
|
||||
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
slog.i << "Submitting cmdbuffer=" << currentbuf->cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< io::endl;
|
||||
slog.i << "Submitting cmdbuffer=" << cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< " fence=" << currentbuf->fence->fence
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
auto& cmdfence = currentbuf->fence;
|
||||
@@ -293,7 +300,7 @@ bool VulkanCommands::flush() {
|
||||
|
||||
#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,44 +333,45 @@ void VulkanCommands::wait() {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer != VK_NULL_HANDLE
|
||||
if (wrapper->buffer() != 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() {
|
||||
VkCommandBuffer buffers[CAPACITY];
|
||||
VkFence fences[CAPACITY];
|
||||
size_t count = 0;
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||
if (wrapper->buffer() == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
VkResult const result = vkWaitForFences(mDevice, 1, &wrapper->fence->fence, VK_TRUE, 0);
|
||||
VkResult const result = vkGetFenceStatus(mDevice, wrapper->fence->fence);
|
||||
if (result != VK_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
buffers[count++] = wrapper->cmdbuffer;
|
||||
wrapper->cmdbuffer = VK_NULL_HANDLE;
|
||||
fences[count++] = wrapper->fence->fence;
|
||||
wrapper->fence->status.store(VK_SUCCESS);
|
||||
wrapper->fence.reset();
|
||||
wrapper->clearResources();
|
||||
++mAvailableCount;
|
||||
wrapper->reset();
|
||||
mAvailableBufferCount++;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
vkFreeCommandBuffers(mDevice, mPool, count, buffers);
|
||||
vkResetFences(mDevice, count, fences);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommands::updateFences() {
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer != VK_NULL_HANDLE) {
|
||||
if (wrapper->buffer() != VK_NULL_HANDLE) {
|
||||
VulkanCmdFence* fence = wrapper->fence.get();
|
||||
if (fence) {
|
||||
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
|
||||
@@ -384,7 +392,7 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
|
||||
#endif
|
||||
|
||||
// TODO: Add group marker color to the Driver API
|
||||
const VkCommandBuffer cmdbuffer = get().cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
|
||||
if (!mGroupMarkers) {
|
||||
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
@@ -409,19 +417,19 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
|
||||
}
|
||||
|
||||
void VulkanCommands::popGroupMarker() {
|
||||
assert_invariant(mGroupMarkers);
|
||||
assert_invariant(mGroupMarkers);
|
||||
|
||||
if (!mGroupMarkers->empty()) {
|
||||
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
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
#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);
|
||||
@@ -437,7 +445,7 @@ void VulkanCommands::popGroupMarker() {
|
||||
}
|
||||
|
||||
void VulkanCommands::insertEventMarker(char const* string, uint32_t len) {
|
||||
VkCommandBuffer const cmdbuffer = get().cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = get().buffer();
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
@@ -447,9 +455,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);
|
||||
}
|
||||
|
||||
@@ -58,9 +58,8 @@ private:
|
||||
|
||||
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
|
||||
struct VulkanCmdFence {
|
||||
VulkanCmdFence(VkDevice device, bool signaled = false);
|
||||
~VulkanCmdFence();
|
||||
const VkDevice device;
|
||||
VulkanCmdFence(VkFence ifence);
|
||||
~VulkanCmdFence() = default;
|
||||
VkFence fence;
|
||||
utils::Condition condition;
|
||||
utils::Mutex mutex;
|
||||
@@ -71,13 +70,10 @@ 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)
|
||||
: mResourceManager(allocator) {}
|
||||
VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, VkCommandPool pool);
|
||||
|
||||
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);
|
||||
@@ -87,12 +83,23 @@ struct VulkanCommandBuffer {
|
||||
mResourceManager.acquire(srcResources);
|
||||
}
|
||||
|
||||
inline void clearResources() {
|
||||
inline void reset() {
|
||||
fence.reset();
|
||||
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.
|
||||
@@ -184,8 +191,9 @@ class VulkanCommands {
|
||||
VkSemaphore mSubmissionSignal = {};
|
||||
VkSemaphore mInjectedSignal = {};
|
||||
utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>> mStorage;
|
||||
VkFence mFences[CAPACITY] = {};
|
||||
VkSemaphore mSubmissionSignals[CAPACITY] = {};
|
||||
size_t mAvailableCount = CAPACITY;
|
||||
uint8_t mAvailableBufferCount = CAPACITY;
|
||||
CommandBufferObserver* mObserver = nullptr;
|
||||
|
||||
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
|
||||
|
||||
@@ -117,8 +117,9 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery* query) {
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
|
||||
vkCmdResetQueryPool(commands->cmdbuffer, mPool, index, 2);
|
||||
vkCmdWriteTimestamp(commands->cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
auto const cmdbuffer = commands->buffer();
|
||||
vkCmdResetQueryPool(cmdbuffer, mPool, index, 2);
|
||||
vkCmdWriteTimestamp(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);
|
||||
@@ -127,7 +128,7 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
|
||||
void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery const* query) {
|
||||
uint32_t const index = query->getStoppingQueryIndex();
|
||||
vkCmdWriteTimestamp(commands->cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
vkCmdWriteTimestamp(commands->buffer(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
}
|
||||
|
||||
VulkanTimestamps::QueryResult VulkanTimestamps::getResult(VulkanTimerQuery const* query) {
|
||||
|
||||
@@ -691,16 +691,15 @@ 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_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();
|
||||
if (cmdfence->status.load() == VK_SUCCESS) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
VkResult result =
|
||||
vkWaitForFences(mPlatform->getDevice(), 1, &cmdfence->fence, VK_TRUE, 0);
|
||||
return result == VK_SUCCESS ? FenceStatus::CONDITION_SATISFIED : FenceStatus::TIMEOUT_EXPIRED;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
|
||||
@@ -873,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.cmdbuffer, p.buffer, byteOffset, p.size);
|
||||
ib->buffer.loadFromCpu(commands.buffer(), p.buffer, byteOffset, p.size);
|
||||
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
@@ -884,7 +883,7 @@ void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescript
|
||||
|
||||
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
commands.acquire(bo);
|
||||
bo->buffer.loadFromCpu(commands.cmdbuffer, bd.buffer, byteOffset, bd.size);
|
||||
bo->buffer.loadFromCpu(commands.buffer(), bd.buffer, byteOffset, bd.size);
|
||||
|
||||
scheduleDestroy(std::move(bd));
|
||||
}
|
||||
@@ -895,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.cmdbuffer, bd.buffer, byteOffset, bd.size);
|
||||
bo->buffer.loadFromCpu(commands.buffer(), bd.buffer, byteOffset, bd.size);
|
||||
mResourceManager.acquire(bo);
|
||||
scheduleDestroy(std::move(bd));
|
||||
}
|
||||
@@ -1021,7 +1020,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
// the non-sampling case.
|
||||
bool samplingDepthAttachment = false;
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT;
|
||||
@@ -1247,7 +1246,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
|
||||
void VulkanDriver::endRenderPass(int) {
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands.cmdbuffer;
|
||||
VkCommandBuffer cmdbuffer = commands.buffer();
|
||||
vkCmdEndRenderPass(cmdbuffer);
|
||||
|
||||
VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
@@ -1299,7 +1298,7 @@ void VulkanDriver::nextSubpass(int) {
|
||||
assert_invariant(renderTarget);
|
||||
assert_invariant(mCurrentRenderPass.params.subpassMask);
|
||||
|
||||
vkCmdNextSubpass(mCommands->get().cmdbuffer, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdNextSubpass(mCommands->get().buffer(), VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
|
||||
++mCurrentRenderPass.currentSubpass);
|
||||
@@ -1484,7 +1483,7 @@ void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, V
|
||||
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
const uint32_t instanceCount) {
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->cmdbuffer;
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
|
||||
Handle<HwProgram> programHandle = pipelineState.program;
|
||||
|
||||
@@ -96,10 +96,6 @@ struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
|
||||
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
|
||||
|
||||
inline void terminate() {
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
|
||||
|
||||
private:
|
||||
@@ -114,9 +110,6 @@ 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;
|
||||
};
|
||||
@@ -124,9 +117,7 @@ struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
|
||||
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage);
|
||||
void terminate() {
|
||||
buffer.terminate();
|
||||
}
|
||||
|
||||
VulkanBuffer buffer;
|
||||
const BufferObjectBinding bindingType;
|
||||
};
|
||||
|
||||
@@ -166,7 +166,7 @@ bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
|
||||
}
|
||||
|
||||
bool VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) noexcept {
|
||||
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
|
||||
PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
|
||||
|
||||
@@ -678,7 +678,7 @@ void VulkanPipelineCache::terminate() noexcept {
|
||||
mDummyMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) {
|
||||
void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
|
||||
// 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
|
||||
// VK_MAX_PIPELINE_AGE flush events in the past, then we can be sure that it is no longer
|
||||
|
||||
@@ -101,10 +101,6 @@ 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,6 +164,10 @@ private:
|
||||
public:
|
||||
using const_iterator = FixedSizeArray::const_iterator;
|
||||
|
||||
inline ~FixedCapacityResourceSet() {
|
||||
clear();
|
||||
}
|
||||
|
||||
inline const_iterator begin() {
|
||||
if (mInd == 0) {
|
||||
return mArray.cend();
|
||||
|
||||
@@ -111,7 +111,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
|
||||
VkImageAspectFlags const aspectFlags = getImageAspect(vkformat);
|
||||
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
|
||||
VkCommandBuffer const cmdbuffer = mCommands->get().buffer();
|
||||
|
||||
// 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
|
||||
|
||||
@@ -79,7 +79,7 @@ void VulkanSwapChain::update() {
|
||||
|
||||
void VulkanSwapChain::present() {
|
||||
if (!mHeadless) {
|
||||
VkCommandBuffer const cmdbuf = mCommands->get().cmdbuffer;
|
||||
VkCommandBuffer const cmdbuf = mCommands->get().buffer();
|
||||
VkImageSubresourceRange const subresources{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
|
||||
@@ -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.cmdbuffer;
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
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.cmdbuffer;
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
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.cmdbuffer;
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(this);
|
||||
|
||||
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
||||
|
||||
@@ -211,66 +211,5 @@ int runTests() {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
void getPixelInfo(PixelDataFormat format, PixelDataType type, size_t& outComponents, int& outBpp) {
|
||||
assert_invariant(type != PixelDataType::COMPRESSED);
|
||||
switch (format) {
|
||||
case PixelDataFormat::UNUSED:
|
||||
case PixelDataFormat::R:
|
||||
case PixelDataFormat::R_INTEGER:
|
||||
case PixelDataFormat::DEPTH_COMPONENT:
|
||||
case PixelDataFormat::ALPHA:
|
||||
outComponents = 1;
|
||||
break;
|
||||
case PixelDataFormat::RG:
|
||||
case PixelDataFormat::RG_INTEGER:
|
||||
case PixelDataFormat::DEPTH_STENCIL:
|
||||
outComponents = 2;
|
||||
break;
|
||||
case PixelDataFormat::RGB:
|
||||
case PixelDataFormat::RGB_INTEGER:
|
||||
outComponents = 3;
|
||||
break;
|
||||
case PixelDataFormat::RGBA:
|
||||
case PixelDataFormat::RGBA_INTEGER:
|
||||
outComponents = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
outBpp = outComponents;
|
||||
switch (type) {
|
||||
case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings
|
||||
case PixelDataType::UBYTE:
|
||||
case PixelDataType::BYTE:
|
||||
// nothing to do
|
||||
break;
|
||||
case PixelDataType::USHORT:
|
||||
case PixelDataType::SHORT:
|
||||
case PixelDataType::HALF:
|
||||
outBpp *= 2;
|
||||
break;
|
||||
case PixelDataType::UINT:
|
||||
case PixelDataType::INT:
|
||||
case PixelDataType::FLOAT:
|
||||
outBpp *= 4;
|
||||
break;
|
||||
case PixelDataType::UINT_10F_11F_11F_REV:
|
||||
// Special case, format must be RGB and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::UINT_2_10_10_10_REV:
|
||||
// Special case, format must be RGBA and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGBA);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::USHORT_565:
|
||||
// Special case, format must be RGB and uses 2 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace test
|
||||
|
||||
|
||||
@@ -75,12 +75,6 @@ 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
filament/backend/test/BackendTestUtils.h
Normal file
174
filament/backend/test/BackendTestUtils.h
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TNT_BACKENDTESTUTILS_H
|
||||
#define TNT_BACKENDTESTUTILS_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
inline void getPixelInfo(PixelDataFormat format, PixelDataType type, size_t& outComponents, int& outBpp) {
|
||||
assert_invariant(type != PixelDataType::COMPRESSED);
|
||||
switch (format) {
|
||||
case PixelDataFormat::UNUSED:
|
||||
case PixelDataFormat::R:
|
||||
case PixelDataFormat::R_INTEGER:
|
||||
case PixelDataFormat::DEPTH_COMPONENT:
|
||||
case PixelDataFormat::ALPHA:
|
||||
outComponents = 1;
|
||||
break;
|
||||
case PixelDataFormat::RG:
|
||||
case PixelDataFormat::RG_INTEGER:
|
||||
case PixelDataFormat::DEPTH_STENCIL:
|
||||
outComponents = 2;
|
||||
break;
|
||||
case PixelDataFormat::RGB:
|
||||
case PixelDataFormat::RGB_INTEGER:
|
||||
outComponents = 3;
|
||||
break;
|
||||
case PixelDataFormat::RGBA:
|
||||
case PixelDataFormat::RGBA_INTEGER:
|
||||
outComponents = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
outBpp = outComponents;
|
||||
switch (type) {
|
||||
case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings
|
||||
case PixelDataType::UBYTE:
|
||||
case PixelDataType::BYTE:
|
||||
// nothing to do
|
||||
break;
|
||||
case PixelDataType::USHORT:
|
||||
case PixelDataType::SHORT:
|
||||
case PixelDataType::HALF:
|
||||
outBpp *= 2;
|
||||
break;
|
||||
case PixelDataType::UINT:
|
||||
case PixelDataType::INT:
|
||||
case PixelDataType::FLOAT:
|
||||
outBpp *= 4;
|
||||
break;
|
||||
case PixelDataType::UINT_10F_11F_11F_REV:
|
||||
// Special case, format must be RGB and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::UINT_2_10_10_10_REV:
|
||||
// Special case, format must be RGBA and uses 4 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGBA);
|
||||
outBpp = 4;
|
||||
break;
|
||||
case PixelDataType::USHORT_565:
|
||||
// Special case, format must be RGB and uses 2 bytes
|
||||
assert_invariant(format == PixelDataFormat::RGB);
|
||||
outBpp = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ComponentType>
|
||||
static void fillCheckerboard(void* buffer, size_t size, size_t stride, size_t components,
|
||||
ComponentType value) {
|
||||
ComponentType* row = (ComponentType*)buffer;
|
||||
int p = 0;
|
||||
for (int r = 0; r < size; r++) {
|
||||
ComponentType* pixel = row;
|
||||
for (int col = 0; col < size; col++) {
|
||||
// Generate a checkerboard pattern.
|
||||
if ((p & 0x0010) ^ ((p / size) & 0x0010)) {
|
||||
// Turn on the first component (red).
|
||||
pixel[0] = value;
|
||||
}
|
||||
pixel += components;
|
||||
p++;
|
||||
}
|
||||
row += stride * components;
|
||||
}
|
||||
}
|
||||
|
||||
static PixelBufferDescriptor checkerboardPixelBuffer(PixelDataFormat format, PixelDataType type,
|
||||
size_t size, size_t bufferPadding = 0) {
|
||||
size_t components; int bpp;
|
||||
getPixelInfo(format, type, components, bpp);
|
||||
|
||||
size_t bufferSize = size + bufferPadding * 2;
|
||||
uint8_t* buffer = (uint8_t*) calloc(1, bufferSize * bufferSize * bpp);
|
||||
|
||||
uint8_t* ptr = buffer + (bufferSize * bufferPadding * bpp) + (bufferPadding * bpp);
|
||||
|
||||
switch (type) {
|
||||
case PixelDataType::BYTE:
|
||||
fillCheckerboard<int8_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::UBYTE:
|
||||
fillCheckerboard<uint8_t>(ptr, size, bufferSize, components, 0xFF);
|
||||
break;
|
||||
|
||||
case PixelDataType::SHORT:
|
||||
fillCheckerboard<int16_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::USHORT:
|
||||
fillCheckerboard<uint16_t>(ptr, size, bufferSize, components, 1u);
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, components, 1u);
|
||||
break;
|
||||
|
||||
case PixelDataType::INT:
|
||||
fillCheckerboard<int32_t>(ptr, size, bufferSize, components, 1);
|
||||
break;
|
||||
|
||||
case PixelDataType::FLOAT:
|
||||
fillCheckerboard<float>(ptr, size, bufferSize, components, 1.0f);
|
||||
break;
|
||||
|
||||
case PixelDataType::HALF:
|
||||
fillCheckerboard<math::half>(ptr, size, bufferSize, components, math::half(1.0f));
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT_2_10_10_10_REV:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, 1, 0xC00003FF /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::USHORT_565:
|
||||
fillCheckerboard<uint16_t>(ptr, size, bufferSize, 1, 0xF800 /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::UINT_10F_11F_11F_REV:
|
||||
fillCheckerboard<uint32_t>(ptr, size, bufferSize, 1, 0x000003C0 /* red */);
|
||||
break;
|
||||
|
||||
case PixelDataType::COMPRESSED:
|
||||
break;
|
||||
}
|
||||
|
||||
PixelBufferDescriptor descriptor(buffer, bufferSize * bufferSize * bpp, format, type,
|
||||
1, bufferPadding, bufferPadding, bufferSize, [](void* buffer, size_t size, void* user) {
|
||||
free(buffer);
|
||||
}, nullptr);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#endif // TNT_BACKENDTESTUTILS_H
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "BackendTestUtils.h"
|
||||
|
||||
#include "private/filament/SamplerInterfaceBlock.h"
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
@@ -113,91 +114,7 @@ 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) {
|
||||
|
||||
171
filament/backend/test/test_MipLevels.cpp
Normal file
171
filament/backend/test/test_MipLevels.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
)");
|
||||
|
||||
}
|
||||
|
||||
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 samples a texture.
|
||||
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);
|
||||
auto program = 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::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));
|
||||
}
|
||||
|
||||
api.setMinMaxLevels(texture, 1, 3);
|
||||
|
||||
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 = program;
|
||||
state.rasterState.colorWrite = true;
|
||||
state.rasterState.depthWrite = false;
|
||||
state.rasterState.depthFunc = SamplerCompareFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
|
||||
api.beginFrame(0, 0);
|
||||
|
||||
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 blue.
|
||||
TrianglePrimitive triangle(api);
|
||||
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.finish();
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "BackendTestUtils.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
|
||||
@@ -245,6 +245,7 @@ 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;
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ math::mat4f ShadowMap::getPointLightViewMatrix(backend::TextureCubemapFace face,
|
||||
}
|
||||
|
||||
ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
const FScene::LightSoa& lightData, size_t index,
|
||||
FScene::LightSoa const& lightData, size_t index,
|
||||
filament::CameraInfo const& camera,
|
||||
ShadowMapInfo const& shadowMapInfo,
|
||||
SceneInfo const& sceneInfo) noexcept {
|
||||
@@ -131,19 +131,6 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
else params.options.shadowFarHint = dzf * dz + camera.zf;
|
||||
#endif
|
||||
|
||||
// Adjust the camera's projection for the light's shadowFar
|
||||
const mat4f cullingProjection{ [&](auto p) {
|
||||
if (params.options.shadowFar > 0.0f) {
|
||||
float const n = camera.zn;
|
||||
float const f = params.options.shadowFar;
|
||||
// orthographic projection
|
||||
assert_invariant(std::abs(p[2].w) <= std::numeric_limits<float>::epsilon());
|
||||
p[2].z = 2.0f / (n - f);
|
||||
p[3].z = (f + n) / (n - f);
|
||||
}
|
||||
return p;
|
||||
}(camera.cullingProjection) };
|
||||
|
||||
const auto direction = params.options.transform * lightData.elementAt<FScene::DIRECTION>(index);
|
||||
|
||||
/*
|
||||
@@ -164,7 +151,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
// view frustum vertices in world-space
|
||||
float3 wsViewFrustumVertices[8];
|
||||
const mat4f worldToClipMatrix = cullingProjection * camera.view;
|
||||
const mat4f worldToClipMatrix = camera.cullingProjection * camera.view;
|
||||
const Frustum wsFrustum(worldToClipMatrix);
|
||||
computeFrustumCorners(wsViewFrustumVertices, inverse(worldToClipMatrix), sceneInfo.csNearFar);
|
||||
|
||||
@@ -216,7 +203,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
}
|
||||
|
||||
// Now that we know the znear (-lsLightFrustumBounds.max.z), adjust the light's position such
|
||||
// that znear = 0, this is only need for VSM, but doesn't hurt PCF.
|
||||
// that znear = 0, this is only needed for VSM, but doesn't hurt PCF.
|
||||
const mat4f Mv = getDirectionalLightViewMatrix(direction, direction * -lsLightFrustumBounds.max.z);
|
||||
|
||||
// near / far planes are specified relative to the direction the eye is looking at
|
||||
@@ -240,8 +227,11 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
const float4 shadowReceiverVolumeBoundingSphere = computeBoundingSphere(
|
||||
wsShadowReceiversVolume.getCorners().data(), 8);
|
||||
|
||||
// in stable mode we simply take the view volume, bounding sphere
|
||||
viewVolumeBoundingSphere = computeBoundingSphere(wsViewFrustumVertices, 8);
|
||||
// in stable mode we simply take the view volume bounding sphere, but we calculate it
|
||||
// in view space, so that it's perfectly stable.
|
||||
float3 vertices[8];
|
||||
computeFrustumCorners(vertices, inverse(camera.cullingProjection), sceneInfo.csNearFar);
|
||||
viewVolumeBoundingSphere = computeBoundingSphere(vertices, 8);
|
||||
|
||||
if (shadowReceiverVolumeBoundingSphere.w < viewVolumeBoundingSphere.w) {
|
||||
|
||||
@@ -320,45 +310,65 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
//
|
||||
// In LiPSM mode, we're using the warped space here.
|
||||
|
||||
Aabb bounds;
|
||||
if (params.options.stable && viewVolumeBoundingSphere.w > 0) {
|
||||
bounds = compute2DBounds(Mv, viewVolumeBoundingSphere);
|
||||
} else {
|
||||
bounds = compute2DBounds(WLMpMv, wsClippedShadowReceiverVolume.data(), vertexCount);
|
||||
}
|
||||
lsLightFrustumBounds.min.xy = bounds.min.xy;
|
||||
lsLightFrustumBounds.max.xy = bounds.max.xy;
|
||||
|
||||
float2 s, o;
|
||||
if (params.options.stable) {
|
||||
// in stable mode we can't do anything that can change the scaling of the texture
|
||||
if (viewVolumeBoundingSphere.w > 0) {
|
||||
s = 1.0f / viewVolumeBoundingSphere.w;
|
||||
o = mat4f::project(LMpMv * camera.model, viewVolumeBoundingSphere.xyz).xy;
|
||||
} else {
|
||||
Aabb const bounds = compute2DBounds(LMpMv,
|
||||
wsClippedShadowReceiverVolume.data(), vertexCount);
|
||||
if (UTILS_UNLIKELY((bounds.min.x >= bounds.max.x) || (bounds.min.y >= bounds.max.y))) {
|
||||
// this could happen if the only thing visible is a perfectly horizontal or
|
||||
// vertical thin line
|
||||
mHasVisibleShadows = false;
|
||||
return {};
|
||||
}
|
||||
assert_invariant(bounds.min.x < bounds.max.x);
|
||||
assert_invariant(bounds.min.y < bounds.max.y);
|
||||
|
||||
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
|
||||
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
|
||||
|
||||
// Quantize the scale in world-space units. This value can be very small because
|
||||
// if it wasn't for floating-point imprecision, the scale would be a constant.
|
||||
double2 const quantizer = 0.0625;
|
||||
s = 1.0 / (ceil(1.0 / (s * quantizer)) * quantizer);
|
||||
}
|
||||
} else {
|
||||
Aabb const bounds = compute2DBounds(WLMpMv,
|
||||
wsClippedShadowReceiverVolume.data(), vertexCount);
|
||||
lsLightFrustumBounds.min.xy = bounds.min.xy;
|
||||
lsLightFrustumBounds.max.xy = bounds.max.xy;
|
||||
// For directional lights, we further constraint the light frustum to the
|
||||
// intersection of the shadow casters & shadow receivers in light-space.
|
||||
// ** This relies on the 1-texel shadow map border **
|
||||
if (engine.debug.shadowmap.focus_shadowcasters) {
|
||||
intersectWithShadowCasters(lsLightFrustumBounds, WLMpMv, wsShadowCastersVolume);
|
||||
}
|
||||
if (UTILS_UNLIKELY((lsLightFrustumBounds.min.x >= lsLightFrustumBounds.max.x) ||
|
||||
(lsLightFrustumBounds.min.y >= lsLightFrustumBounds.max.y))) {
|
||||
// this could happen if the only thing visible is a perfectly horizontal or
|
||||
// vertical thin line
|
||||
mHasVisibleShadows = false;
|
||||
return {};
|
||||
}
|
||||
assert_invariant(lsLightFrustumBounds.min.x < lsLightFrustumBounds.max.x);
|
||||
assert_invariant(lsLightFrustumBounds.min.y < lsLightFrustumBounds.max.y);
|
||||
|
||||
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
|
||||
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
|
||||
|
||||
// TODO: we could quantize `s` here to give some stability when lispsm is disabled,
|
||||
// however, the quantization paramater should probably be user settable.
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY((lsLightFrustumBounds.min.x >= lsLightFrustumBounds.max.x) ||
|
||||
(lsLightFrustumBounds.min.y >= lsLightFrustumBounds.max.y))) {
|
||||
// this could happen if the only thing visible is a perfectly horizontal or
|
||||
// vertical thin line
|
||||
mHasVisibleShadows = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
assert_invariant(lsLightFrustumBounds.min.x < lsLightFrustumBounds.max.x);
|
||||
assert_invariant(lsLightFrustumBounds.min.y < lsLightFrustumBounds.max.y);
|
||||
|
||||
// compute focus scale and offset
|
||||
float2 s = 2.0f / float2(lsLightFrustumBounds.max.xy - lsLightFrustumBounds.min.xy);
|
||||
float2 o = -s * float2(lsLightFrustumBounds.max.xy + lsLightFrustumBounds.min.xy) * 0.5f;
|
||||
// adjust offset for scale
|
||||
o = -s * o;
|
||||
|
||||
if (params.options.stable) {
|
||||
// Use the world origin as reference point, fixed w.r.t. the camera
|
||||
snapLightFrustum(s, o, Mv, camera.worldOrigin[3].xyz,
|
||||
1.0f / float(shadowMapInfo.shadowDimension));
|
||||
snapLightFrustum(s, o, LMpMv,
|
||||
sceneInfo.wsShadowCastersVolume.center(), shadowMapInfo.shadowDimension);
|
||||
}
|
||||
|
||||
const mat4f F(mat4f::row_major_init {
|
||||
@@ -377,8 +387,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
// Computes St the transform to use in the shader to access the shadow map texture
|
||||
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
|
||||
const backend::Viewport viewport = getViewport();
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, viewport);
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||
const mat4f St = math::highPrecisionMultiply(Mt, S);
|
||||
|
||||
ShadowMap::ShaderParameters shaderParameters;
|
||||
@@ -400,7 +409,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
shaderParameters.lightSpace = computeVsmLightSpaceMatrix(St, Mv, znear, zfar);
|
||||
}
|
||||
|
||||
shaderParameters.scissorNormalized = getViewportNormalized(shadowMapInfo);
|
||||
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
|
||||
|
||||
// We apply the constant bias in world space (as opposed to light-space) to account
|
||||
// for perspective and lispsm shadow maps. This also allows us to do this at zero-cost
|
||||
@@ -433,9 +442,8 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
|
||||
assert_invariant(shadowMapInfo.textureDimension == mOptions->mapSize);
|
||||
|
||||
// Final shadow transform
|
||||
const backend::Viewport viewport = getViewport();
|
||||
const mat4f S = math::highPrecisionMultiply(Mp, Mv);
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, viewport);
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||
const mat4f St = math::highPrecisionMultiply(Mt, S);
|
||||
|
||||
// TODO: focus projection
|
||||
@@ -462,7 +470,7 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
|
||||
shaderParameters.lightSpace = computeVsmLightSpaceMatrix(St, Mv, nearPlane, farPlane);
|
||||
}
|
||||
|
||||
shaderParameters.scissorNormalized = getViewportNormalized(shadowMapInfo);
|
||||
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
|
||||
|
||||
const float3 direction = -transpose(Mv)[2].xyz;
|
||||
const float constantBias = shadowMapInfo.vsm ? 0.0f : params.options.constantBias;
|
||||
@@ -825,21 +833,39 @@ void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
|
||||
}
|
||||
|
||||
void ShadowMap::snapLightFrustum(float2& s, float2& o,
|
||||
mat4f const& Mv, float3 worldOrigin, float2 shadowMapResolution) noexcept {
|
||||
mat4f const& Mv, double3 wsSnapCoords, int2 resolution) noexcept {
|
||||
|
||||
auto fmod = [](float2 x, float2 y) -> float2 {
|
||||
auto mod = [](float x, float y) -> float { return std::fmod(x, y); };
|
||||
return float2{ mod(x[0], y[0]), mod(x[1], y[1]) };
|
||||
auto proj = [](mat4 m, double3 v) -> double3 {
|
||||
// for directional light p.w == 1, exactly
|
||||
auto p = m * v;
|
||||
assert_invariant(p.w == 1.0);
|
||||
return p.xyz;
|
||||
};
|
||||
|
||||
// This snaps the shadow map bounds to texels.
|
||||
// The 2.0 comes from Mv having a NDC in the range -1,1 (so a range of 2).
|
||||
const float2 r = 2.0f * shadowMapResolution;
|
||||
o -= fmod(o, r);
|
||||
auto fract = [](auto v) {
|
||||
using namespace std;
|
||||
using T = decltype(v);
|
||||
return fmod(v, T{1});
|
||||
};
|
||||
|
||||
const mat4 F(mat4::row_major_init {
|
||||
s.x, 0.0, 0.0, o.x,
|
||||
0.0, s.y, 0.0, o.y,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
});
|
||||
|
||||
// The (resolution * 0.5) comes from Mv having a NDC in the range -1,1 (so a range of 2).
|
||||
|
||||
// focused light-space
|
||||
mat4 const FMv{ F * Mv };
|
||||
|
||||
// This offsets the texture coordinates, so it has a fixed offset w.r.t the world
|
||||
const float2 lsOrigin = mat4f::project(Mv, worldOrigin).xy * s;
|
||||
o -= fmod(lsOrigin, r);
|
||||
double2 const lsOrigin = proj(FMv, wsSnapCoords).xy;
|
||||
double2 const d = (fract(lsOrigin * resolution * 0.5) * 2.0) / resolution;
|
||||
|
||||
// adjust offset
|
||||
o -= d;
|
||||
}
|
||||
|
||||
size_t ShadowMap::intersectFrustumWithBox(
|
||||
@@ -1042,7 +1068,7 @@ bool ShadowMap::intersectSegmentWithPlanarQuad(float3& UTILS_RESTRICT p,
|
||||
}
|
||||
|
||||
float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) const noexcept {
|
||||
uint16_t shadowDimension) noexcept {
|
||||
// The Jacobian of the transformation from texture-to-world is the matrix itself for
|
||||
// orthographic projections. We just need to inverse worldToShadowTexture,
|
||||
// which is guaranteed to be orthographic.
|
||||
@@ -1057,7 +1083,7 @@ float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture,
|
||||
}
|
||||
|
||||
float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
|
||||
uint16_t shadowDimension) const noexcept {
|
||||
uint16_t shadowDimension) noexcept {
|
||||
// Here we compute the Jacobian of inverse(MbMtF * Wp).
|
||||
// The expression below has been computed with Mathematica. However, it's not very hard,
|
||||
// albeit error-prone, to do it by hand because MbMtF is a linear transform.
|
||||
@@ -1078,7 +1104,7 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
|
||||
|
||||
constexpr bool JACOBIAN_ESTIMATE = false;
|
||||
if constexpr (JACOBIAN_ESTIMATE) {
|
||||
// this estimates the Jacobian -- this is a lot heavier. This is mostly for reference
|
||||
// This estimates the Jacobian -- this is a lot heavier. This is mostly for reference
|
||||
// and testing.
|
||||
const mat4f Si(inverse(MbMtF * Wp));
|
||||
const float3 p0 = mat4f::project(Si, p);
|
||||
@@ -1217,33 +1243,58 @@ void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getViewport() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the
|
||||
// texture. This can only happen for the directional light when "focus shadow casters is used".
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp).
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
return { border, border, dim - 2u * border, dim - 2u * border };
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getScissor() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the
|
||||
// texture. This can only happen for the directional light when "focus shadow casters is used".
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp), so we
|
||||
// don't scissor the border, so it gets filled with correct neighboring texels.
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
return { border, border, dim - 2u * border, dim - 2u * border };
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
default:
|
||||
return { 0, 0, dim, dim };
|
||||
}
|
||||
}
|
||||
|
||||
math::float4 ShadowMap::getViewportNormalized(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
const auto [l, b, w, h] = getViewport();
|
||||
const float texel = 1.0f / float(shadowMapInfo.atlasDimension);
|
||||
const float4 v = float4{ l, b, l + w, b + h } * texel;
|
||||
math::float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
float border; // shadowmap border in texels
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
// For directional lights, we need to allow the sampling to reach the border, it
|
||||
// happens when "focus shadow casters" is used for instance.
|
||||
border = 0.5f;
|
||||
break;
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
// For spot and point light, this is equal to the viewport. i.e. the valid
|
||||
// texels are inside the viewport (w/ 1-texel border), the border will be used
|
||||
// for bilinear filtering.
|
||||
border = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
float const texel = 1.0f / float(shadowMapInfo.atlasDimension);
|
||||
float const dim = float(mOptions->mapSize);
|
||||
float const l = border;
|
||||
float const b = border;
|
||||
float const w = dim - 2.0f * border;
|
||||
float const h = dim - 2.0f * border;
|
||||
float4 const v = float4{ l, b, l + w, b + h } * texel;
|
||||
if (shadowMapInfo.textureSpaceFlipped) {
|
||||
// this is equivalent to calling uvToRenderTargetUV() in the shader *after* clamping
|
||||
// texture coordinates to this normalized viewport.
|
||||
|
||||
@@ -222,7 +222,7 @@ private:
|
||||
const math::float3& dir);
|
||||
|
||||
static inline void snapLightFrustum(math::float2& s, math::float2& o,
|
||||
math::mat4f const& Mv, math::float3 worldOrigin, math::float2 shadowMapResolution) noexcept;
|
||||
math::mat4f const& Mv, math::double3 wsSnapCoords, math::int2 resolution) noexcept;
|
||||
|
||||
static inline void computeFrustumCorners(math::float3* out,
|
||||
const math::mat4f& projectionViewInverse, math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
|
||||
@@ -281,13 +281,13 @@ private:
|
||||
static math::mat4f computeVsmLightSpaceMatrix(const math::mat4f& lightSpacePcf,
|
||||
const math::mat4f& Mv, float znear, float zfar) noexcept;
|
||||
|
||||
math::float4 getViewportNormalized(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||
math::float4 getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||
|
||||
float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) const noexcept;
|
||||
static float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) noexcept;
|
||||
|
||||
float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
|
||||
uint16_t shadowDimension) const noexcept;
|
||||
static float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
|
||||
uint16_t shadowDimension) noexcept;
|
||||
|
||||
static constexpr const Segment sBoxSegments[12] = {
|
||||
{ 0, 1 }, { 1, 3 }, { 3, 2 }, { 2, 0 },
|
||||
|
||||
@@ -435,8 +435,9 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
}
|
||||
|
||||
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept {
|
||||
|
||||
FScene* scene = view.getScene();
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
@@ -444,6 +445,24 @@ 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),
|
||||
@@ -504,19 +523,13 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
splitPercentages[i] = options.cascadeSplitPositions[i - 1];
|
||||
}
|
||||
|
||||
const CascadeSplits::Params p{
|
||||
const CascadeSplits splits({
|
||||
.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).
|
||||
@@ -530,7 +543,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
|
||||
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
||||
|
||||
// when computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
||||
// When computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0);
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
|
||||
private:
|
||||
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept;
|
||||
|
||||
ShadowMapManager::ShadowTechnique updateSpotShadowMaps(FEngine& engine,
|
||||
@@ -145,17 +145,8 @@ 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.
|
||||
@@ -186,9 +177,6 @@ private:
|
||||
|
||||
SoftShadowOptions mSoftShadowOptions;
|
||||
|
||||
CascadeSplits::Params mCascadeSplitParams;
|
||||
CascadeSplits mCascadeSplits;
|
||||
|
||||
mutable TypedUniformBuffer<ShadowUib> mShadowUb;
|
||||
backend::Handle<backend::HwBufferObject> mShadowUbh;
|
||||
|
||||
@@ -204,8 +192,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;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.42.2"
|
||||
spec.version = "1.43.0"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.42.2/filament-v1.42.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.43.0/filament-v1.43.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
#define TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
#define TNT_FILAMENT_SAMPLE_CAMERA_MANIPULATOR_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 42;
|
||||
static constexpr size_t MATERIAL_VERSION = 43;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -35,7 +35,7 @@ for example, `filament-20181009-linux.tgz`.
|
||||
|
||||
Create a file, `main.cpp`, in the same directory with the following contents:
|
||||
|
||||
```
|
||||
```c++
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <iostream>
|
||||
@@ -85,7 +85,7 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory.
|
||||
|
||||
### Linux
|
||||
|
||||
```
|
||||
```make
|
||||
FILAMENT_LIBS=-lfilamat -lfilabridge -lshaders -lutils -lsmol-v
|
||||
CC=clang++
|
||||
|
||||
@@ -103,7 +103,7 @@ clean:
|
||||
|
||||
### macOS
|
||||
|
||||
```
|
||||
```make
|
||||
FILAMENT_LIBS=-lfilamat -lfilabridge -lshaders -lutils -lsmol-v
|
||||
CC=clang++
|
||||
|
||||
@@ -129,7 +129,7 @@ flags](https://docs.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-tim
|
||||
When building Filamat from source, the `USE_STATIC_CRT` CMake option can be
|
||||
used to change the run-time library version.
|
||||
|
||||
```
|
||||
```make
|
||||
FILAMENT_LIBS=lib/x86_64/mt/filamat.lib lib/x86_64/mt/filabridge.lib lib/x86_64/mt/shaders.lib \
|
||||
lib/x86_64/mt/utils.lib lib/x86_64/mt/smol-v.lib
|
||||
CC=clang-cl.exe
|
||||
@@ -164,7 +164,7 @@ and invoke `nmake` instead of `make`.
|
||||
For simplicity, this demo doesn't do anything useful with the built material package. To use the
|
||||
material with Filament, pass the material package's data into a Filament Material builder:
|
||||
|
||||
```
|
||||
```c++
|
||||
Package package = builder.build();
|
||||
filament::Material* myMaterial = Material::Builder()
|
||||
.package(package.getData(), package.getSize())
|
||||
@@ -186,7 +186,7 @@ In addition, `filamat_lite` only performs a simple text match to determine which
|
||||
`MaterialInputs` structure are set. The `material` input variable must also always be refered to by
|
||||
the name `material`.
|
||||
|
||||
```
|
||||
```glsl
|
||||
void anotherFunction(inout MaterialInputs m) {
|
||||
// Incorrect! The MaterialInputs is being referred to by the name "m".
|
||||
m.metallic = 0.0;
|
||||
|
||||
@@ -16,6 +16,7 @@ set(PUBLIC_HDRS
|
||||
include/gltfio/FilamentInstance.h
|
||||
include/gltfio/MaterialProvider.h
|
||||
include/gltfio/NodeManager.h
|
||||
include/gltfio/TrsTransformManager.h
|
||||
include/gltfio/ResourceLoader.h
|
||||
include/gltfio/TextureProvider.h
|
||||
include/gltfio/math.h
|
||||
@@ -35,10 +36,12 @@ set(SRCS
|
||||
src/FilamentAsset.cpp
|
||||
src/FilamentInstance.cpp
|
||||
src/FNodeManager.h
|
||||
src/FTrsTransformManager.h
|
||||
src/GltfEnums.h
|
||||
src/Ktx2Provider.cpp
|
||||
src/MaterialProvider.cpp
|
||||
src/NodeManager.cpp
|
||||
src/TrsTransformManager.cpp
|
||||
src/ResourceLoader.cpp
|
||||
src/StbProvider.cpp
|
||||
src/TangentsJob.cpp
|
||||
@@ -186,8 +189,7 @@ if (NOT WEBGL AND NOT ANDROID AND NOT IOS)
|
||||
# ==================================================================================================
|
||||
# Compiler flags
|
||||
# ==================================================================================================
|
||||
if (MSVC)
|
||||
else()
|
||||
if (NOT MSVC)
|
||||
target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register)
|
||||
endif()
|
||||
|
||||
@@ -225,7 +227,9 @@ if (TNT_DEV AND NOT WEBGL AND NOT ANDROID AND NOT IOS)
|
||||
add_dependencies(${TEST_TARGET} test_gltfio_files)
|
||||
|
||||
target_link_libraries(${TEST_TARGET} PRIVATE ${TARGET} filament filabridge gtest uberarchive)
|
||||
target_compile_options(${TEST_TARGET} PRIVATE -Wno-deprecated-register)
|
||||
if (NOT MSVC)
|
||||
target_compile_options(${TEST_TARGET} PRIVATE -Wno-deprecated-register)
|
||||
endif()
|
||||
set_target_properties(${TEST_TARGET} PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
|
||||
|
||||
114
libs/gltfio/include/gltfio/TrsTransformManager.h
Normal file
114
libs/gltfio/include/gltfio/TrsTransformManager.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef GLTFIO_TRSTRANSFORMMANAGER_H
|
||||
#define GLTFIO_TRSTRANSFORMMANAGER_H
|
||||
|
||||
#include <filament/FilamentAPI.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/EntityInstance.h>
|
||||
#include <math/quat.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/mat4.h>
|
||||
|
||||
using namespace filament::math;
|
||||
|
||||
namespace utils {
|
||||
class Entity;
|
||||
} // namespace utils
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
class FTrsTransformManager;
|
||||
|
||||
/**
|
||||
* TrsTransformManager is used to add entities with glTF-specific trs information.
|
||||
*
|
||||
* Trs information here just used for Animation, DON'T use for transform.
|
||||
*/
|
||||
class UTILS_PUBLIC TrsTransformManager {
|
||||
public:
|
||||
using Instance = utils::EntityInstance<TrsTransformManager>;
|
||||
using Entity = utils::Entity;
|
||||
|
||||
/**
|
||||
* Returns whether a particular Entity is associated with a component of this TrsTransformManager
|
||||
* @param e An Entity.
|
||||
* @return true if this Entity has a component associated with this manager.
|
||||
*/
|
||||
bool hasComponent(Entity e) const noexcept;
|
||||
|
||||
/**
|
||||
* Gets an Instance representing the trs transform component associated with the given Entity.
|
||||
* @param e An Entity.
|
||||
* @return An Instance object, which represents the trs transform component associated with the Entity e.
|
||||
* @note Use Instance::isValid() to make sure the component exists.
|
||||
* @see hasComponent()
|
||||
*/
|
||||
Instance getInstance(Entity e) const noexcept;
|
||||
|
||||
/**
|
||||
* Creates a trs transform component and associates it with the given entity.
|
||||
* @param entity An Entity to associate a trs transform component with.
|
||||
* @param translation The translation to initialize the trs transform component with.
|
||||
* @param rotation The rotation to initialize the trs transform component with.
|
||||
* @param scale The scale to initialize the trs transform component with.
|
||||
*
|
||||
* If this component already exists on the given entity, it is first destroyed as if
|
||||
* destroy(Entity e) was called.
|
||||
*
|
||||
* @see destroy()
|
||||
*/
|
||||
void create(Entity entity);
|
||||
void create(Entity entity, const float3& translation, const quatf& rotation,
|
||||
const float3& scale); //!< \overload
|
||||
|
||||
/**
|
||||
* Destroys this component from the given entity.
|
||||
* @param e An entity.
|
||||
*
|
||||
* @see create()
|
||||
*/
|
||||
void destroy(Entity e) noexcept;
|
||||
|
||||
void setTranslation(Instance ci, const float3& translation) noexcept;
|
||||
const float3& getTranslation(Instance ci) const noexcept;
|
||||
|
||||
void setRotation(Instance ci, const quatf& rotation) noexcept;
|
||||
const quatf& getRotation(Instance ci) const noexcept;
|
||||
|
||||
void setScale(Instance ci, const float3& scale) noexcept;
|
||||
const float3& getScale(Instance ci) const noexcept;
|
||||
|
||||
void setTrs(Instance ci, const float3& translation, const quatf& rotation,
|
||||
const float3& scale) noexcept;
|
||||
const mat4f getTransform(Instance ci) const noexcept;
|
||||
|
||||
protected:
|
||||
TrsTransformManager() noexcept = default;
|
||||
~TrsTransformManager() = default;
|
||||
|
||||
public:
|
||||
TrsTransformManager(TrsTransformManager const&) = delete;
|
||||
TrsTransformManager(TrsTransformManager&&) = delete;
|
||||
TrsTransformManager& operator=(TrsTransformManager const&) = delete;
|
||||
TrsTransformManager& operator=(TrsTransformManager&&) = delete;
|
||||
};
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
#endif // GLTFIO_TRSTRANSFORMMANAGER_H
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "FFilamentAsset.h"
|
||||
#include "FFilamentInstance.h"
|
||||
#include "FTrsTransformManager.h"
|
||||
#include "downcast.h"
|
||||
|
||||
#include <filament/VertexBuffer.h>
|
||||
@@ -74,6 +75,7 @@ struct AnimatorImpl {
|
||||
FFilamentInstance* instance = nullptr;
|
||||
RenderableManager* renderableManager;
|
||||
TransformManager* transformManager;
|
||||
TrsTransformManager* trsTransformManager;
|
||||
vector<float> weights;
|
||||
FixedCapacityVector<mat4f> crossFade;
|
||||
void addChannels(const FixedCapacityVector<Entity>& nodeMap, const cgltf_animation& srcAnim,
|
||||
@@ -190,6 +192,7 @@ Animator::Animator(FFilamentAsset const* asset, FFilamentInstance* instance) {
|
||||
mImpl->instance = instance;
|
||||
mImpl->renderableManager = &asset->mEngine->getRenderableManager();
|
||||
mImpl->transformManager = &asset->mEngine->getTransformManager();
|
||||
mImpl->trsTransformManager = asset->getTrsTransformManager();
|
||||
|
||||
const cgltf_data* srcAsset = asset->mSourceAsset->hierarchy;
|
||||
const cgltf_animation* srcAnims = srcAsset->animations;
|
||||
@@ -429,20 +432,13 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
size_t nextIndex) {
|
||||
const Sampler* sampler = channel.sourceData;
|
||||
const TimeValues& times = sampler->times;
|
||||
TrsTransformManager::Instance trsNode = trsTransformManager->getInstance(channel.targetEntity);
|
||||
TransformManager::Instance node = transformManager->getInstance(channel.targetEntity);
|
||||
|
||||
// Perform the interpolation. This is a simple but inefficient implementation; Filament
|
||||
// stores transforms as mat4's but glTF animation is based on TRS (translation rotation
|
||||
// scale).
|
||||
mat4f xform = transformManager->getTransform(node);
|
||||
float3 scale;
|
||||
quatf rotation;
|
||||
float3 translation;
|
||||
decomposeMatrix(xform, &translation, &rotation, &scale);
|
||||
|
||||
switch (channel.transformType) {
|
||||
|
||||
case Channel::SCALE: {
|
||||
float3 scale;
|
||||
const float3* srcVec3 = (const float3*) sampler->values.data();
|
||||
if (sampler->interpolation == Sampler::CUBIC) {
|
||||
float3 vert0 = srcVec3[prevIndex * 3 + 1];
|
||||
@@ -453,10 +449,12 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
} else {
|
||||
scale = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]);
|
||||
}
|
||||
trsTransformManager->setScale(trsNode, scale);
|
||||
break;
|
||||
}
|
||||
|
||||
case Channel::TRANSLATION: {
|
||||
float3 translation;
|
||||
const float3* srcVec3 = (const float3*) sampler->values.data();
|
||||
if (sampler->interpolation == Sampler::CUBIC) {
|
||||
float3 vert0 = srcVec3[prevIndex * 3 + 1];
|
||||
@@ -467,10 +465,12 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
} else {
|
||||
translation = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]);
|
||||
}
|
||||
trsTransformManager->setTranslation(trsNode, translation);
|
||||
break;
|
||||
}
|
||||
|
||||
case Channel::ROTATION: {
|
||||
quatf rotation;
|
||||
const quatf* srcQuat = (const quatf*) sampler->values.data();
|
||||
if (sampler->interpolation == Sampler::CUBIC) {
|
||||
quatf vert0 = srcQuat[prevIndex * 3 + 1];
|
||||
@@ -481,6 +481,7 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
} else {
|
||||
rotation = slerp(srcQuat[prevIndex], srcQuat[nextIndex], t);
|
||||
}
|
||||
trsTransformManager->setRotation(trsNode, rotation);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -519,8 +520,7 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
|
||||
}
|
||||
}
|
||||
|
||||
xform = composeMatrix(translation, rotation, scale);
|
||||
transformManager->setTransform(node, xform);
|
||||
transformManager->setTransform(node, trsTransformManager->getTransform(trsNode));
|
||||
}
|
||||
|
||||
void AnimatorImpl::resetBoneMatrices(FFilamentInstance* instance) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "FFilamentAsset.h"
|
||||
#include "FNodeManager.h"
|
||||
#include "FTrsTransformManager.h"
|
||||
#include "GltfEnums.h"
|
||||
|
||||
#include <filament/Box.h>
|
||||
@@ -295,6 +296,7 @@ public:
|
||||
MaterialProvider& mMaterials;
|
||||
Engine& mEngine;
|
||||
FNodeManager mNodeManager;
|
||||
FTrsTransformManager mTrsTransformManager;
|
||||
|
||||
// Transient state used only for the asset currently being loaded:
|
||||
FFilamentAsset* mAsset;
|
||||
@@ -400,7 +402,8 @@ void FAssetLoader::createRootAsset(const cgltf_data* srcAsset) {
|
||||
#endif
|
||||
|
||||
mDummyBufferObject = nullptr;
|
||||
mAsset = new FFilamentAsset(&mEngine, mNameManager, &mEntityManager, &mNodeManager, srcAsset);
|
||||
mAsset = new FFilamentAsset(&mEngine, mNameManager, &mEntityManager, &mNodeManager,
|
||||
&mTrsTransformManager, srcAsset);
|
||||
|
||||
// It is not an error for a glTF file to have zero scenes.
|
||||
mAsset->mScenes.clear();
|
||||
@@ -563,7 +566,9 @@ void FAssetLoader::recurseEntities(const cgltf_data* srcAsset, const cgltf_node*
|
||||
quatf* rotation = (quatf*) &node->rotation[0];
|
||||
float3* scale = (float3*) &node->scale[0];
|
||||
float3* translation = (float3*) &node->translation[0];
|
||||
localTransform = composeMatrix(*translation, *rotation, *scale);
|
||||
mTrsTransformManager.create(entity, *translation, *rotation, *scale);
|
||||
localTransform = mTrsTransformManager.getTransform(
|
||||
mTrsTransformManager.getInstance(entity));
|
||||
}
|
||||
|
||||
auto parentTransform = mTransformManager.getInstance(parent);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <gltfio/FilamentAsset.h>
|
||||
#include <gltfio/NodeManager.h>
|
||||
#include <gltfio/TrsTransformManager.h>
|
||||
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/IndexBuffer.h>
|
||||
@@ -110,9 +111,9 @@ using MeshCache = utils::FixedCapacityVector<utils::FixedCapacityVector<Primitiv
|
||||
struct FFilamentAsset : public FilamentAsset {
|
||||
FFilamentAsset(Engine* engine, utils::NameComponentManager* names,
|
||||
utils::EntityManager* entityManager, NodeManager* nodeManager,
|
||||
const cgltf_data* srcAsset) :
|
||||
TrsTransformManager* trsTransformManager, const cgltf_data* srcAsset) :
|
||||
mEngine(engine), mNameManager(names), mEntityManager(entityManager),
|
||||
mNodeManager(nodeManager),
|
||||
mNodeManager(nodeManager), mTrsTransformManager(trsTransformManager),
|
||||
mSourceAsset(new SourceAsset {(cgltf_data*)srcAsset}),
|
||||
mTextures(srcAsset->textures_count),
|
||||
mMeshCache(srcAsset->meshes_count) {}
|
||||
@@ -195,6 +196,10 @@ struct FFilamentAsset : public FilamentAsset {
|
||||
return mEngine;
|
||||
}
|
||||
|
||||
TrsTransformManager* getTrsTransformManager() const noexcept {
|
||||
return mTrsTransformManager;
|
||||
}
|
||||
|
||||
void releaseSourceData() noexcept;
|
||||
|
||||
const void* getSourceAsset() const noexcept {
|
||||
@@ -242,6 +247,7 @@ struct FFilamentAsset : public FilamentAsset {
|
||||
utils::NameComponentManager* const mNameManager;
|
||||
utils::EntityManager* const mEntityManager;
|
||||
NodeManager* const mNodeManager;
|
||||
TrsTransformManager* const mTrsTransformManager;
|
||||
std::vector<utils::Entity> mEntities; // sorted such that renderables come first
|
||||
std::vector<utils::Entity> mLightEntities;
|
||||
std::vector<utils::Entity> mCameraEntities;
|
||||
|
||||
165
libs/gltfio/src/FTrsTransformManager.h
Normal file
165
libs/gltfio/src/FTrsTransformManager.h
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef GLTFIO_FTRSTRANSFORMMANAGER_H
|
||||
#define GLTFIO_FTRSTRANSFORMMANAGER_H
|
||||
|
||||
#include "downcast.h"
|
||||
#include "gltfio/math.h"
|
||||
#include "math/quat.h"
|
||||
#include "utils/debug.h"
|
||||
|
||||
#include <gltfio/TrsTransformManager.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/SingleInstanceComponentManager.h>
|
||||
#include <utils/Entity.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
class UTILS_PRIVATE FTrsTransformManager : public TrsTransformManager {
|
||||
public:
|
||||
using Instance = TrsTransformManager::Instance;
|
||||
|
||||
FTrsTransformManager() noexcept {}
|
||||
|
||||
~FTrsTransformManager() noexcept {
|
||||
assert_invariant(mManager.getComponentCount() == 0);
|
||||
}
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
bool hasComponent(utils::Entity e) const noexcept {
|
||||
return mManager.hasComponent(e);
|
||||
}
|
||||
|
||||
Instance getInstance(utils::Entity e) const noexcept {
|
||||
return Instance(mManager.getInstance(e));
|
||||
}
|
||||
|
||||
void create(utils::Entity entity) {
|
||||
create(entity, float3{}, quatf{}, float3{1});
|
||||
}
|
||||
|
||||
void create(utils::Entity entity, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) {
|
||||
if (UTILS_UNLIKELY(mManager.hasComponent(entity))) {
|
||||
destroy(entity);
|
||||
}
|
||||
UTILS_UNUSED_IN_RELEASE Instance ci = mManager.addComponent(entity);
|
||||
assert_invariant(ci);
|
||||
|
||||
if (ci) {
|
||||
setTrs(ci, translation, rotation, scale);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(utils::Entity e) noexcept {
|
||||
if (Instance ci = mManager.getInstance(e); ci) {
|
||||
mManager.removeComponent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void gc(utils::EntityManager& em) noexcept {
|
||||
mManager.gc(em);
|
||||
}
|
||||
|
||||
void setTranslation(Instance ci, const float3& translation) noexcept {
|
||||
assert_invariant(ci.isValid());
|
||||
mManager[ci].translation = translation;
|
||||
}
|
||||
|
||||
const float3& getTranslation(Instance ci) const noexcept {
|
||||
return mManager[ci].translation;
|
||||
}
|
||||
|
||||
void setRotation(Instance ci, const quatf& rotation) noexcept {
|
||||
assert_invariant(ci.isValid());
|
||||
mManager[ci].rotation = rotation;
|
||||
}
|
||||
|
||||
const quatf& getRotation(Instance ci) const noexcept {
|
||||
return mManager[ci].rotation;
|
||||
}
|
||||
|
||||
void setScale(Instance ci, const float3& scale) noexcept {
|
||||
assert_invariant(ci.isValid());
|
||||
mManager[ci].scale = scale;
|
||||
}
|
||||
|
||||
const float3& getScale(Instance ci) const noexcept {
|
||||
return mManager[ci].scale;
|
||||
}
|
||||
|
||||
void setTrs(Instance ci, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) noexcept {
|
||||
setTranslation(ci, translation);
|
||||
setRotation(ci, rotation);
|
||||
setScale(ci, scale);
|
||||
}
|
||||
|
||||
const mat4f getTransform(Instance ci) const noexcept {
|
||||
return composeMatrix(getTranslation(ci), getRotation(ci), getScale(ci));
|
||||
}
|
||||
|
||||
private:
|
||||
enum {
|
||||
TRANSLATION,
|
||||
ROTATION,
|
||||
SCALE,
|
||||
};
|
||||
|
||||
using Base = utils::SingleInstanceComponentManager<
|
||||
float3,
|
||||
quatf,
|
||||
float3>;
|
||||
|
||||
struct Sim : public Base {
|
||||
using Base::gc;
|
||||
using Base::swap;
|
||||
|
||||
typename Base::SoA& getSoA() { return mData; }
|
||||
|
||||
struct Proxy {
|
||||
UTILS_ALWAYS_INLINE
|
||||
Proxy(Base& sim, utils::EntityInstanceBase::Type i) noexcept :
|
||||
translation{ sim, i } { }
|
||||
|
||||
union {
|
||||
Field<TRANSLATION> translation;
|
||||
Field<ROTATION> rotation;
|
||||
Field<SCALE> scale;
|
||||
};
|
||||
};
|
||||
|
||||
UTILS_ALWAYS_INLINE Proxy operator[](Instance i) noexcept {
|
||||
return { *this, i };
|
||||
}
|
||||
UTILS_ALWAYS_INLINE const Proxy operator[](Instance i) const noexcept {
|
||||
return { const_cast<Sim&>(*this), i };
|
||||
}
|
||||
};
|
||||
|
||||
Sim mManager;
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(TrsTransformManager)
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
#endif // GLTFIO_FTRSTRANSFORMMANAGER_H
|
||||
@@ -60,6 +60,11 @@ FFilamentAsset::~FFilamentAsset() {
|
||||
|
||||
}
|
||||
|
||||
// Destroy gltfio trs transform components.
|
||||
for (auto entity : mEntities) {
|
||||
mTrsTransformManager->destroy(entity);
|
||||
}
|
||||
|
||||
// Destroy all renderable, light, transform, and camera components,
|
||||
// then destroy the actual entities. This includes instances.
|
||||
if (!mDetachedFilamentComponents) {
|
||||
|
||||
98
libs/gltfio/src/TrsTransformManager.cpp
Normal file
98
libs/gltfio/src/TrsTransformManager.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "FTrsTransformManager.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "downcast.h"
|
||||
#include "gltfio/TrsTransformManager.h"
|
||||
|
||||
using namespace utils;
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
using Instance = TrsTransformManager::Instance;
|
||||
|
||||
void FTrsTransformManager::terminate() noexcept {
|
||||
auto& manager = mManager;
|
||||
if (!manager.empty()) {
|
||||
#ifndef NDEBUG
|
||||
utils::slog.d << "cleaning up " << manager.getComponentCount()
|
||||
<< " leaked trs transform components" << utils::io::endl;
|
||||
#endif
|
||||
while (!manager.empty()) {
|
||||
Instance ci = manager.end() - 1;
|
||||
manager.removeComponent(manager.getEntity(ci));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TrsTransformManager::hasComponent(Entity e) const noexcept {
|
||||
return downcast(this)->hasComponent(e);
|
||||
}
|
||||
|
||||
Instance TrsTransformManager::getInstance(Entity e) const noexcept {
|
||||
return downcast(this)->getInstance(e);
|
||||
}
|
||||
|
||||
void TrsTransformManager::create(Entity entity) {
|
||||
downcast(this)->create(entity);
|
||||
}
|
||||
|
||||
void TrsTransformManager::create(Entity entity, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) {
|
||||
downcast(this)->create(entity, translation, rotation, scale);
|
||||
}
|
||||
|
||||
void TrsTransformManager::destroy(Entity e) noexcept {
|
||||
downcast(this)->destroy(e);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setTranslation(Instance ci, const float3& translation) noexcept {
|
||||
downcast(this)->setTranslation(ci, translation);
|
||||
}
|
||||
|
||||
const float3& TrsTransformManager::getTranslation(Instance ci) const noexcept {
|
||||
return downcast(this)->getTranslation(ci);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setRotation(Instance ci, const quatf& rotation) noexcept {
|
||||
downcast(this)->setRotation(ci, rotation);
|
||||
}
|
||||
|
||||
const quatf& TrsTransformManager::getRotation(Instance ci) const noexcept {
|
||||
return downcast(this)->getRotation(ci);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setScale(Instance ci, const float3& scale) noexcept {
|
||||
downcast(this)->setScale(ci, scale);
|
||||
}
|
||||
|
||||
const float3& TrsTransformManager::getScale(Instance ci) const noexcept {
|
||||
return downcast(this)->getScale(ci);
|
||||
}
|
||||
|
||||
void TrsTransformManager::setTrs(Instance ci, const float3& translation,
|
||||
const quatf& rotation, const float3& scale) noexcept {
|
||||
downcast(this)->setTrs(ci, translation, rotation, scale);
|
||||
}
|
||||
|
||||
const mat4f TrsTransformManager::getTransform(Instance ci) const noexcept {
|
||||
return downcast(this)->getTransform(ci);
|
||||
}
|
||||
|
||||
} // namespace filament::gltfio
|
||||
@@ -443,6 +443,37 @@ private:
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
friend inline
|
||||
VECTOR<T> MATH_PURE fmod(VECTOR<T> const& x, VECTOR<U> const& y) {
|
||||
VECTOR<T> r;
|
||||
for (size_t i = 0; i < r.size(); i++) {
|
||||
r[i] = std::fmod(x[i], y[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
friend inline
|
||||
VECTOR<T> MATH_PURE remainder(VECTOR<T> const& x, VECTOR<U> const& y) {
|
||||
VECTOR<T> r;
|
||||
for (size_t i = 0; i < r.size(); i++) {
|
||||
r[i] = std::remainder(x[i], y[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
friend inline
|
||||
VECTOR<T> MATH_PURE remquo(VECTOR<T> const& x, VECTOR<U> const& y,
|
||||
VECTOR<int>* q) {
|
||||
VECTOR<T> r;
|
||||
for (size_t i = 0; i < r.size(); i++) {
|
||||
r[i] = std::remquo(x[i], y[i], &((*q)[i]));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
friend inline VECTOR<T> MATH_PURE inversesqrt(VECTOR<T> v) {
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
v[i] = T(1) / std::sqrt(v[i]);
|
||||
|
||||
@@ -578,11 +578,9 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight*
|
||||
ibl->setRotation(math::mat3f::rotation(settings.iblRotation, math::float3 { 0, 1, 0 }));
|
||||
}
|
||||
for (size_t i = 0; i < sceneLightCount; i++) {
|
||||
light = lm->getInstance(sceneLights[i]);
|
||||
if (lm->isSpotLight(light)) {
|
||||
lm->setShadowCaster(light, settings.enableShadows);
|
||||
}
|
||||
lm->setShadowOptions(light, settings.shadowOptions);
|
||||
auto const li = lm->getInstance(sceneLights[i]);
|
||||
lm->setShadowCaster(li, settings.enableShadows);
|
||||
lm->setShadowOptions(li, settings.shadowOptions);
|
||||
}
|
||||
view->setSoftShadowOptions(settings.softShadowOptions);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ struct App {
|
||||
|
||||
bool actualSize = false;
|
||||
bool originIsFarAway = false;
|
||||
float originDistance = 6378137; // Earth's radius in [m]
|
||||
|
||||
struct Scene {
|
||||
Entity groundPlane;
|
||||
@@ -759,6 +760,7 @@ int main(int argc, char** argv) {
|
||||
ImGui::Checkbox("Disable buffer padding", debug.getPropertyAddress<bool>("d.renderer.disable_buffer_padding"));
|
||||
ImGui::Checkbox("Camera at origin", debug.getPropertyAddress<bool>("d.view.camera_at_origin"));
|
||||
ImGui::Checkbox("Far Origin", &app.originIsFarAway);
|
||||
ImGui::SliderFloat("Origin", &app.originDistance, 0, 10000000);
|
||||
auto dataSource = debug.getDataSource("d.view.frame_info");
|
||||
if (dataSource.data) {
|
||||
ImGuiExt::PlotLinesSeries("FrameInfo", 6,
|
||||
@@ -956,7 +958,7 @@ int main(int argc, char** argv) {
|
||||
tcm.setParent(tcm.getInstance(camera.getEntity()), root);
|
||||
tcm.setParent(tcm.getInstance(app.asset->getRoot()), root);
|
||||
tcm.setParent(tcm.getInstance(view->getFogEntity()), root);
|
||||
tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? 1e6f : 0.0f }));
|
||||
tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? app.originDistance : 0.0f }));
|
||||
|
||||
// Check if color grading has changed.
|
||||
ColorGradingSettings& options = app.viewer->getSettings().view.colorGrading;
|
||||
|
||||
@@ -109,7 +109,7 @@ highp vec4 getSpotLightSpacePosition(int index, highp vec3 dir, highp float zLig
|
||||
// for spotlights, the bias depends on z
|
||||
float bias = shadowUniforms.shadows[index].normalBias * zLight;
|
||||
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldGeometricNormalVector(),
|
||||
dir, bias, lightFromWorldMatrix);
|
||||
}
|
||||
#endif
|
||||
@@ -141,7 +141,7 @@ highp vec4 getCascadeLightSpacePosition(int cascade) {
|
||||
return vertex_lightSpacePosition;
|
||||
}
|
||||
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldGeometricNormalVector(),
|
||||
frameUniforms.lightDirection,
|
||||
shadowUniforms.shadows[cascade].normalBias,
|
||||
shadowUniforms.shadows[cascade].lightFromWorldMatrix);
|
||||
|
||||
@@ -45,8 +45,8 @@ end
|
||||
|
||||
Then run:
|
||||
|
||||
```
|
||||
$ pod install
|
||||
```shell
|
||||
pod install
|
||||
```
|
||||
|
||||
Close the project and then re-open the newly created HelloCocoaPods.xcworkspace file.
|
||||
|
||||
@@ -7,9 +7,9 @@ The tool can consume a HDR environment map in latlong format (equirectilinear) a
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ cmgen [options] <input-file>
|
||||
$ cmgen [options] <uv[N]>
|
||||
```shell
|
||||
cmgen [options] <input-file>
|
||||
cmgen [options] <uv[N]>
|
||||
```
|
||||
|
||||
## Supported input formats
|
||||
@@ -21,6 +21,7 @@ $ cmgen [options] <uv[N]>
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
--help, -h
|
||||
Print this message
|
||||
--license
|
||||
@@ -61,4 +62,4 @@ $ cmgen [options] <uv[N]>
|
||||
Roughness pre-filter into <dir>
|
||||
--sh-shader
|
||||
Generate irradiance SH for shader code
|
||||
|
||||
```
|
||||
|
||||
@@ -14,8 +14,8 @@ identified by an offset and count in the index buffer. Each part can have its ow
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ filamesh source_mesh destination_mesh
|
||||
```shell
|
||||
filamesh source_mesh destination_mesh
|
||||
```
|
||||
|
||||
## Format
|
||||
|
||||
@@ -5,6 +5,6 @@ used for debug purpose only.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ matinfo [options] <material file>
|
||||
```shell
|
||||
matinfo [options] <material file>
|
||||
```
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ mipgen [options] <input_file> <output_pattern>
|
||||
```shell
|
||||
mipgen [options] <input_file> <output_pattern>
|
||||
```
|
||||
|
||||
Run `mipgen --help` for more information about available options.
|
||||
|
||||
@@ -11,8 +11,8 @@ grazing angles. See Hoffman 2019, "Fresnel Equations Considered Harmful".
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ specular-color <spectral data file>
|
||||
```shell
|
||||
specular-color <spectral data file>
|
||||
```
|
||||
|
||||
The spectral data files can be obtained from
|
||||
@@ -20,12 +20,12 @@ The spectral data files can be obtained from
|
||||
|
||||
For instance, to compute the base color of gold:
|
||||
|
||||
```
|
||||
$ specular-color data/gold.txt
|
||||
```shell
|
||||
specular-color data/gold.txt
|
||||
```
|
||||
|
||||
To set the second angle, use `-a` to specify the angle in degrees:
|
||||
|
||||
```
|
||||
$ specular-color -a 75 data/gold.txt
|
||||
```shell
|
||||
specular-color -a 75 data/gold.txt
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user